diff --git a/app/assets/javascripts/alchemy/admin.js b/app/assets/javascripts/alchemy/admin.js
index 597bfed13d..98c553cc1a 100644
--- a/app/assets/javascripts/alchemy/admin.js
+++ b/app/assets/javascripts/alchemy/admin.js
@@ -6,7 +6,6 @@
//= require alchemy/templates
//= require alchemy/alchemy.dialog
//= require alchemy/alchemy.confirm_dialog
-//= require alchemy/alchemy.elements_window
//= require alchemy/alchemy.fixed_elements
//= require alchemy/alchemy.image_overlay
//= require alchemy/alchemy.link_dialog
diff --git a/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee b/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee
deleted file mode 100644
index cd0b617b20..0000000000
--- a/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee
+++ /dev/null
@@ -1,108 +0,0 @@
-window.Alchemy = {} if typeof(window.Alchemy) is 'undefined'
-
-# Adds buttons into a toolbar inside of overlay windows
-Alchemy.ToolbarButton = (options) ->
- $btn = $("")
- if options.align
- $btn.addClass(options.class)
- if options.buttonId
- $btn.attr(id: options.buttonId)
- $lnk = $("")
- if options.hotkey
- $lnk.attr('data-alchemy-hotkey', options.hotkey)
- $lnk.on "click", (e) ->
- e.preventDefault()
- options.onClick(e)
- return
- $lnk.append ""
- $btn.append $lnk
- $btn
-
-Alchemy.ElementsWindow =
-
- init: (url, options, callback) ->
- @hidden = false
- @$body = $('body')
- @element_window = $('
')
- @element_area = $('')
- @url = url
- @options = options
- @callback = callback
- @element_window.append @createToolbar(options.toolbarButtons)
- @element_window.append @element_area
- Alchemy.GUI.init(@element_window)
- @button = $('#element_window_button')
- @button.on "click", =>
- @hide()
- false
-
- window.requestAnimationFrame =>
- spinner = new Alchemy.Spinner('medium')
- spinner.spin @element_area[0]
-
- window.addEventListener 'message', (event) =>
- data = event.data
- if data?.message == 'Alchemy.focusElementEditor'
- element = document.getElementById("element_#{data.element_id}")
- Alchemy.ElementsWindow.show()
- element?.focusElement()
- true
-
- @$body.on "click", (evt) =>
- unless evt.target.closest(".element-editor")
- @element_area.find('.element-editor').removeClass('selected')
- Alchemy.PreviewWindow.postMessage(message: 'Alchemy.blurElements')
- return
-
- $('#main_content').append(@element_window)
- @show()
- @reload()
-
- createToolbar: (buttons) ->
- @toolbar = $('')
- buttons.push
- label: Alchemy.t("Collapse all elements")
- iconClass: "contract-up-down-line"
- align: "end"
- class: "right"
- onClick: =>
- $("alchemy-element-editor:not([compact]):not([fixed])").each () ->
- @collapse()
- for btn in buttons
- @toolbar.append Alchemy.ToolbarButton(btn)
- @toolbar.append @collapseAllBtn
-
- reload: ->
- $.get @url, (data) =>
- @element_area.html data
- Alchemy.SortableElements()
- if @callback
- @callback.call()
- .fail (xhr, status, error) =>
- Alchemy.Dialog::show_error(xhr, error, @element_area)
-
- hide: ->
- @$body.removeClass('elements-window-visible');
- @hidden = true
- @toggleButton()
-
- show: ->
- @$body.addClass('elements-window-visible');
- @hidden = false
- @toggleButton()
-
- toggleButton: ->
- if @hidden
- @button.find('label').text(@options.texts.showElements)
- @button.find('alchemy-icon').attr("name", "menu-fold")
- @button.off('click')
- @button.on "click", =>
- @show()
- false
- else
- @button.find('label').text(@options.texts.hideElements)
- @button.find('alchemy-icon').attr("name", "menu-unfold")
- @button.off('click')
- @button.on "click", =>
- @hide()
- false
diff --git a/app/assets/stylesheets/alchemy/elements.scss b/app/assets/stylesheets/alchemy/elements.scss
index 206cbc55a4..6677d68fa3 100644
--- a/app/assets/stylesheets/alchemy/elements.scss
+++ b/app/assets/stylesheets/alchemy/elements.scss
@@ -1,8 +1,9 @@
-#alchemy_elements_window {
+alchemy-elements-window {
position: absolute;
right: 0;
top: $top-menu-height;
z-index: 20;
+ display: block;
width: calc(100vw - #{$collapsed-main-menu-width});
height: calc(100vh - #{$top-menu-height});
border-left: $default-border;
diff --git a/app/javascript/alchemy_admin.js b/app/javascript/alchemy_admin.js
index 0e94f13757..be1a37835a 100644
--- a/app/javascript/alchemy_admin.js
+++ b/app/javascript/alchemy_admin.js
@@ -26,6 +26,7 @@ import "alchemy_admin/components/clipboard_button"
import "alchemy_admin/components/datepicker"
import "alchemy_admin/components/dialog_link"
import "alchemy_admin/components/element_editor"
+import "alchemy_admin/components/elements_window"
import "alchemy_admin/components/message"
import "alchemy_admin/components/growl"
import "alchemy_admin/components/icon"
diff --git a/app/javascript/alchemy_admin/components/elements_window.js b/app/javascript/alchemy_admin/components/elements_window.js
new file mode 100644
index 0000000000..bbb09694eb
--- /dev/null
+++ b/app/javascript/alchemy_admin/components/elements_window.js
@@ -0,0 +1,86 @@
+class ElementsWindow extends HTMLElement {
+ #visible = true
+
+ constructor() {
+ super()
+ this.#attachEvents()
+ }
+
+ connectedCallback() {
+ document.body.classList.add("elements-window-visible")
+ this.toggleButton?.addEventListener("click", (evt) => {
+ evt.preventDefault()
+ this.toggle()
+ })
+ if (window.location.hash) {
+ document
+ .querySelector(window.location.hash)
+ ?.trigger("FocusElementEditor.Alchemy")
+ }
+ Alchemy.SortableElements()
+ }
+
+ collapseAllElements() {
+ this.querySelectorAll(
+ "alchemy-element-editor:not([compact]):not([fixed])"
+ ).forEach((editor) => editor.collapse())
+ }
+
+ toggle() {
+ if (this.#visible) {
+ this.hide()
+ } else {
+ this.show()
+ }
+ }
+
+ show() {
+ document.body.classList.add("elements-window-visible")
+ this.#visible = true
+ this.toggleButton.closest("sl-tooltip").content = Alchemy.t("Hide elements")
+ this.toggleButton
+ .querySelector("alchemy-icon")
+ .setAttribute("name", "menu-unfold")
+ }
+
+ hide() {
+ document.body.classList.remove("elements-window-visible")
+ this.#visible = false
+ this.toggleButton.closest("sl-tooltip").content = Alchemy.t("Show elements")
+ this.toggleButton
+ .querySelector("alchemy-icon")
+ .setAttribute("name", "menu-fold")
+ }
+
+ get collapseButton() {
+ return this.querySelector("#collapse-all-elements-button")
+ }
+
+ get toggleButton() {
+ return document.querySelector("#element_window_button")
+ }
+
+ #attachEvents() {
+ this.collapseButton?.addEventListener("click", () => {
+ this.collapseAllElements()
+ })
+ window.addEventListener("message", (event) => {
+ const data = event.data
+ if (data?.message == "Alchemy.focusElementEditor") {
+ const element = document.getElementById(`element_${data.element_id}`)
+ this.show()
+ element?.focusElement()
+ }
+ })
+ document.body.addEventListener("click", (evt) => {
+ if (!evt.target.closest("alchemy-element-editor")) {
+ this.querySelectorAll("alchemy-element-editor").forEach((editor) => {
+ editor.classList.remove("selected")
+ })
+ Alchemy.PreviewWindow.postMessage({ message: "Alchemy.blurElements" })
+ }
+ })
+ }
+}
+
+customElements.define("alchemy-elements-window", ElementsWindow)
diff --git a/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb b/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb
index 4b355ba722..b7dbb2fd3d 100644
--- a/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb
+++ b/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb
@@ -8,7 +8,7 @@
<%= f.hidden_field :name %>
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
<%= f.hidden_field :parent_element_id, value: element.id %>
-