diff --git a/vendor/magento/module-catalog/Model/Product/Attribute/AttributeSetUnassignValidator.php b/vendor/magento/module-catalog/Model/Product/Attribute/AttributeSetUnassignValidator.php
new file mode 100644
index 0000000000000..783602b09beef
--- /dev/null
+++ b/vendor/magento/module-catalog/Model/Product/Attribute/AttributeSetUnassignValidator.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Copyright 2025 Adobe
+ * All Rights Reserved.
+ */
+declare(strict_types=1);
+
+namespace Magento\Catalog\Model\Product\Attribute;
+
+use Magento\Catalog\Model\Attribute\Config;
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Framework\Exception\LocalizedException;
+
+class AttributeSetUnassignValidator implements AttributeSetUnassignValidatorInterface
+{
+    /**
+     * @var array
+     */
+    private array $unassignable;
+
+    /**
+     * @param Config $attributeConfig
+     */
+    public function __construct(
+        private readonly Config $attributeConfig
+    ) {
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validate(AbstractAttribute $attribute, int $attributeSetId): void
+    {
+        if (!isset($this->unassignable)) {
+            $this->unassignable = $this->attributeConfig->getAttributeNames('unassignable');
+        }
+        if (in_array($attribute->getAttributeCode(), $this->unassignable)) {
+            throw new LocalizedException(
+                __("The system attribute can't be deleted.")
+            );
+        }
+    }
+}
diff --git a/vendor/magento/module-catalog/Model/Product/Attribute/AttributeSetUnassignValidatorInterface.php b/vendor/magento/module-catalog/Model/Product/Attribute/AttributeSetUnassignValidatorInterface.php
new file mode 100644
index 0000000000000..a50935f67f364
--- /dev/null
+++ b/vendor/magento/module-catalog/Model/Product/Attribute/AttributeSetUnassignValidatorInterface.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Copyright 2025 Adobe
+ * All Rights Reserved.
+ */
+declare(strict_types=1);
+
+namespace Magento\Catalog\Model\Product\Attribute;
+
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Model\AbstractModel;
+
+/**
+ * Interface to validate attribute removal from an attribute set
+ */
+interface AttributeSetUnassignValidatorInterface
+{
+    /**
+     * Validate attribute
+     *
+     * @param AbstractModel $attribute
+     * @param int $attributeSetId
+     * @return void
+     * @throws LocalizedException
+     */
+    public function validate(AbstractAttribute $attribute, int $attributeSetId): void;
+}
diff --git a/vendor/magento/module-catalog/Model/Product/Attribute/Group.php b/vendor/magento/module-catalog/Model/Product/Attribute/Group.php
index 1eaa8e5afc42a..71336cbdc7677 100644
--- a/vendor/magento/module-catalog/Model/Product/Attribute/Group.php
+++ b/vendor/magento/module-catalog/Model/Product/Attribute/Group.php
@@ -7,6 +7,8 @@
 namespace Magento\Catalog\Model\Product\Attribute;
 
 use Magento\Framework\Api\AttributeValueFactory;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\LocalizedException;
 
 class Group extends \Magento\Eav\Model\Entity\Attribute\Group
 {
@@ -17,6 +19,11 @@ class Group extends \Magento\Eav\Model\Entity\Attribute\Group
      */
     protected $_attributeCollectionFactory;
 
+    /**
+     * @var AttributeSetUnassignValidatorInterface
+     */
+    private $attributeSetUnassignValidator;
+
     /**
      * Group constructor.
      * @param \Magento\Framework\Model\Context $context
@@ -28,6 +35,8 @@ class Group extends \Magento\Eav\Model\Entity\Attribute\Group
      * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
      * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
      * @param array $data
+     * @param AttributeSetUnassignValidatorInterface|null $attributeSetUnassignValidator
+     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
     public function __construct(
         \Magento\Framework\Model\Context $context,
@@ -38,9 +47,12 @@ public function __construct(
         \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeCollectionFactory,
         \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
         \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
-        array $data = []
+        array $data = [],
+        ?AttributeSetUnassignValidatorInterface $attributeSetUnassignValidator = null
     ) {
         $this->_attributeCollectionFactory = $attributeCollectionFactory;
+        $this->attributeSetUnassignValidator = $attributeSetUnassignValidator
+            ?: ObjectManager::getInstance()->get(AttributeSetUnassignValidatorInterface::class);
         parent::__construct(
             $context,
             $registry,
@@ -72,4 +84,24 @@ public function hasSystemAttributes()
         }
         return $result;
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function beforeDelete()
+    {
+        $attributesCollection = $this->_attributeCollectionFactory->create();
+        $attributesCollection->setAttributeGroupFilter($this->getId());
+        foreach ($attributesCollection as $attribute) {
+            try {
+                $this->attributeSetUnassignValidator->validate($attribute, (int) $attribute->getAttributeSetId());
+            } catch (LocalizedException $e) {
+                throw new LocalizedException(
+                    __("This group contains system attributes." .
+                        " Please move system attributes to another group and try again.")
+                );
+            }
+        }
+        return parent::beforeDelete();
+    }
 }
diff --git a/vendor/magento/module-catalog/Model/ResourceModel/Eav/Attribute.php b/vendor/magento/module-catalog/Model/ResourceModel/Eav/Attribute.php
index 07ce84c7cd62e..403cfec206eb6 100644
--- a/vendor/magento/module-catalog/Model/ResourceModel/Eav/Attribute.php
+++ b/vendor/magento/module-catalog/Model/ResourceModel/Eav/Attribute.php
@@ -8,8 +8,10 @@
 
 use Magento\Catalog\Model\Attribute\Backend\DefaultBackend;
 use Magento\Catalog\Model\Attribute\LockValidatorInterface;
+use Magento\Catalog\Model\Product\Attribute\AttributeSetUnassignValidatorInterface;
 use Magento\Eav\Model\Entity;
 use Magento\Framework\Api\AttributeValueFactory;
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface;
 
 /**
@@ -98,6 +100,11 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute implements
      */
     private $eavAttributeFactory;
 
+    /**
+     * @var AttributeSetUnassignValidatorInterface
+     */
+    private $attributeSetUnassignValidator;
+
     /**
      * @param \Magento\Framework\Model\Context $context
      * @param \Magento\Framework\Registry $registry
@@ -123,6 +130,7 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute implements
      * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
      * @param array $data
      * @param \Magento\Eav\Api\Data\AttributeExtensionFactory|null $eavAttributeFactory
+     * @param AttributeSetUnassignValidatorInterface|null $attributeSetUnassignValidator
      * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
     public function __construct(
@@ -149,7 +157,8 @@ public function __construct(
         \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
         \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
         array $data = [],
-        \Magento\Eav\Api\Data\AttributeExtensionFactory $eavAttributeFactory = null
+        ?\Magento\Eav\Api\Data\AttributeExtensionFactory $eavAttributeFactory = null,
+        ?AttributeSetUnassignValidatorInterface $attributeSetUnassignValidator = null
     ) {
         $this->_indexerEavProcessor = $indexerEavProcessor;
         $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor;
@@ -157,6 +166,8 @@ public function __construct(
         $this->attrLockValidator = $lockValidator;
         $this->eavAttributeFactory = $eavAttributeFactory ?: \Magento\Framework\App\ObjectManager::getInstance()
             ->get(\Magento\Eav\Api\Data\AttributeExtensionFactory::class);
+        $this->attributeSetUnassignValidator = $attributeSetUnassignValidator
+            ?: ObjectManager::getInstance()->get(AttributeSetUnassignValidatorInterface::class);
         parent::__construct(
             $context,
             $registry,
@@ -917,4 +928,16 @@ protected function _getDefaultBackendModel()
 
         return $backend;
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function deleteEntity()
+    {
+        if ($this->getEntityAttributeId()) {
+            $result = $this->_getResource()->getEntityAttribute($this->getEntityAttributeId());
+            $result && $this->attributeSetUnassignValidator->validate($this, (int) $result['attribute_set_id']);
+        }
+        return parent::deleteEntity();
+    }
 }
diff --git a/vendor/magento/module-catalog/etc/di.xml b/vendor/magento/module-catalog/etc/di.xml
index a13175fa78e90..df36733051c0d 100644
--- a/vendor/magento/module-catalog/etc/di.xml
+++ b/vendor/magento/module-catalog/etc/di.xml
@@ -78,6 +78,7 @@
     <preference for="Magento\Catalog\Api\CategoryListDeleteBySkuInterface" type="Magento\Catalog\Model\CategoryLinkRepository"/>
     <preference for="Magento\Theme\CustomerData\MessagesProviderInterface" type="Magento\Catalog\Model\Theme\CustomerData\MessagesProvider"/>
     <preference for="Magento\Catalog\Api\ProductAttributeIsFilterableManagementInterface" type="Magento\Catalog\Model\Product\Attribute\IsFilterableManagement" />
+    <preference for="Magento\Catalog\Model\Product\Attribute\AttributeSetUnassignValidatorInterface" type="Magento\Catalog\Model\Product\Attribute\AttributeSetUnassignValidator" />
     <type name="Magento\Customer\Model\ResourceModel\Visitor">
         <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" />
     </type>
@@ -1351,4 +1352,24 @@
             </argument>
         </arguments>
     </type>
+    <virtualType name="Magento\Catalog\Virtual\Product\Attribute\GroupFactory" type="Magento\Eav\Model\Entity\Attribute\GroupFactory">
+        <arguments>
+            <argument name="instanceName" xsi:type="string">Magento\Catalog\Model\Product\Attribute\Group</argument>
+        </arguments>
+    </virtualType>
+    <virtualType name="Magento\Catalog\Virtual\Product\Attribute\Set" type="Magento\Eav\Model\Entity\Attribute\Set">
+        <arguments>
+            <argument name="attrGroupFactory" xsi:type="object">Magento\Catalog\Virtual\Product\Attribute\GroupFactory</argument>
+        </arguments>
+    </virtualType>
+    <virtualType name="Magento\Catalog\Virtual\Product\Attribute\SetFactory" type="Magento\Eav\Model\Entity\Attribute\SetFactory">
+        <arguments>
+            <argument name="instanceName" xsi:type="string">Magento\Catalog\Virtual\Product\Attribute\Set</argument>
+        </arguments>
+    </virtualType>
+    <type name="Magento\Catalog\Controller\Adminhtml\Product\Set\Save">
+        <arguments>
+            <argument name="attributeSetFactory" xsi:type="object">Magento\Catalog\Virtual\Product\Attribute\SetFactory</argument>
+        </arguments>
+    </type>
 </config>
diff --git a/vendor/magento/module-catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml b/vendor/magento/module-catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
index 03b0a38ca97d0..0a84b7cb247ec 100644
--- a/vendor/magento/module-catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
+++ b/vendor/magento/module-catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
@@ -1,11 +1,12 @@
 <?php
 /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
+ * Copyright 2011 Adobe
+ * All Rights Reserved.
  */
 
 /** @var $block Magento\Catalog\Block\Adminhtml\Product\Attribute\Set\Main */
 /** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
+/** @var \Magento\Framework\Escaper $escaper */
 ?>
 <div class="attribute-set">
 
@@ -33,12 +34,12 @@ script;
     </div>
     <div class="attribute-set-col fieldset-wrapper">
         <div class="fieldset-wrapper-title">
-            <span class="title"><?= $block->escapeHtml(__('Groups')) ?></span>
+            <span class="title"><?= $escaper->escapeHtml(__('Groups')) ?></span>
         </div>
         <?php if (!$block->getIsReadOnly()):?>
             <?= /* @noEscape */ $block->getAddGroupButton() ?>&nbsp;
             <?= /* @noEscape */ $block->getDeleteGroupButton() ?>
-            <p class="note-block"><?= $block->escapeHtml(__('Double click on a group to rename it.')) ?></p>
+            <p class="note-block"><?= $escaper->escapeHtml(__('Double click on a group to rename it.')) ?></p>
         <?php endif; ?>
 
         <?= $block->getSetsFilterHtml() ?>
@@ -46,462 +47,109 @@ script;
     </div>
     <div class="attribute-set-col fieldset-wrapper">
         <div class="fieldset-wrapper-title">
-            <span class="title"><?= $block->escapeHtml(__('Unassigned Attributes')) ?></span>
+            <span class="title"><?= $escaper->escapeHtml(__('Unassigned Attributes')) ?></span>
         </div>
         <div id="tree-div2" class="attribute-set-tree"></div>
-        <?php $readOnly = ($block->getIsReadOnly() ? 'false' : 'true');
+        <?php
+        $readOnly = $block->getIsReadOnly() ? 'true' : 'false';
+        $saveUrl = $escaper->escapeJs($block->getMoveUrl());
         $groupTree = /* @noEscape */ $block->getGroupTreeJson();
         $attributeTreeJson = /* @noEscape */ $block->getAttributeTreeJson();
-        $systemAttributeWarning = $block->escapeJs(
+        $newGroupModalTitle = $escaper->escapeJs($escaper->escapeHtml(__('Add New Group')));
+        $newGroupModalContent = $escaper->escapeJs($escaper->escapeHtml(__('Please enter a new group name.')));
+        $cannotSaveAlert = $escaper->escapeJs(__('Sorry, we\'re unable to complete this request.'));
+        $cannotUnassignAttributeAlert = $escaper->escapeJs(__('You can\'t remove attributes from this attribute set.'));
+        $cannotDeleteGroupAlert =  $escaper->escapeJs(
             __('This group contains system attributes. Please move system attributes to another group and try again.')
         );
-        $scriptString = <<<script
-            define('tree-panel',
-                [
-                    'jquery',
-                    'Magento_Ui/js/modal/prompt',
-                    'Magento_Ui/js/modal/alert',
-                    'jquery/jstree/jquery.jstree',
-                    'prototype'
-                ], function(jQuery, prompt, alert){
-
-                //<![CDATA[
-                var allowDragAndDrop = {$readOnly};
-                var canEditGroups = {$readOnly};
-                var treeDiv1 = jQuery('#tree-div1');
-                var treeDiv2 = jQuery('#tree-div2');
-
-                var TreePanels = function() {
-
-                    return {
-                        init: function() {
-
-                            var treeRoot = [{
-                                text: 'ROOT',
-                                id: '#',
-                                allowDrag: false,
-                                allowDrop: true,
-                                state: { opened: true, loaded: true, selected: false },
-                                li_attr: { class: '' }
-                            }];
-
-                            /**
-                            * Initialize the jstree with tree root
-                            */
-                            treeDiv1.jstree({
-                                core: {
-                                    animation: false,
-                                    check_callback: checkCallback,
-                                    data: treeRoot
-                                },
-                                plugins : [ 'dnd' ],
-                            });
-
-                            var root = treeDiv1.jstree(true).get_node('#');
-                            buildCategoryTree(treeDiv1, root, {$groupTree});
-
-                            //-------------------------------------------------------------
-
-                            var treeRoot2 = [
-                                {
-                                    text: 'ROOT_2',
-                                    id: '#',
-                                    allowDrag: true,
-                                    allowDrop: true,
-                                    state: { opened: true, loaded: true }
-                                }
-                            ];
-
-                            /**
-                            * Initialize the jstree with tree root 2
-                            */
-                            treeDiv2.jstree({
-                                core: {
-                                    data: treeRoot2,
-                                    check_callback: checkCallback,
-                                    themes: {
-                                        dots: false
-                                    }
-                                },
-                                plugins : [ 'dnd' ]
-                            });
-
-                            var root2 = treeDiv2.jstree(true).get_node('#');
-                            buildCategoryTree(treeDiv2, root2, {$attributeTreeJson});
-                        },
-
-                        rebuildTrees: function () {
-                            editSet.req.attributes = [];
-                            rootNode = treeDiv1.jstree(true).get_node('#');
-                            gIterator = 0;
-
-                            rootNode.children.forEach(function (groupNodeId) {
-                                let groupNode = treeDiv1.jstree(true).get_node(groupNodeId);
-                                let newGroupNodeId = groupNode.id.replace(groupNode.parent+'_', '');
-                                editSet.req.groups[gIterator] =
-                                    [newGroupNodeId, groupNode.text.trim(), (gIterator + 1)];
-                                let iterator = 0;
-                                groupNode.children.forEach(function (childNodeId) {
-                                    iterator++;
-                                    childNode = treeDiv1.jstree(true).get_node(childNodeId);
-                                    if(!childNode.id.startsWith(childNode.parent) ) {
-                                        childNode.id = childNode.parent +
-                                            childNode.id.substring(childNode.id.lastIndexOf('_'));
-                                        childNode.li_attr.id = childNode.id;
-                                        childNode.a_attr.id = childNode.id + "_anchor";
-                                    }
-                                    generatedNodeId = childNode.original.attribute_id;
-                                    if( generatedNodeId > 0 ) {
-                                        editSet.req.attributes[generatedNodeId] = new Array(generatedNodeId,
-                                        newGroupNodeId, iterator, childNode.original.entity_id);
-                                    }
-                                });
-
-                                iterator = 0;
-                                gIterator++;
-                            });
-
-                            editSet.req.not_attributes = [];
-                            rootNode = treeDiv2.jstree(true).get_node('#');
-
-                            let iterator = 0;
-                            rootNode.children.forEach(function (childId) {
-                                childNode = treeDiv2.jstree(true).get_node(childId);
-                                generatedNodeId = childNode.original.attribute_id;
-                                if( generatedNodeId > 0 ) {
-                                    editSet.req.not_attributes[iterator] =
-                                     childNode.original.entity_id;
-                                }
-                                iterator ++;
-                            });
-                       }
-                    };
-                }();
-
-                function buildCategoryTree(treeDiv, parent, config) {
-                    if (!config) return;
-                    const treeInstance = treeDiv.jstree(true);
-
-                    for (var i = 0; i < config.length; i++) {
-                        var nodeConfig = config[i];
-
-                        // Create a new node in the jsTree
-                        let newNode = {
-                            text: nodeConfig.text,
-                            id: parent.id+'_'+nodeConfig.id,
-                            attribute_id: nodeConfig.id,
-                            allowDrag: nodeConfig.allowDrag,
-                            allowDrop: nodeConfig.allowDrop,
-                            cls: nodeConfig.cls,
-                            entity_id: nodeConfig.entity_id,
-                            is_user_defined: nodeConfig.is_user_defined,
-                            li_attr: { class: nodeConfig.cls }
-                        };
-                        const parentTree = treeInstance.create_node(parent, newNode, 'last');
-
-                        if (nodeConfig.children) {
-                            for (var j = 0; j < nodeConfig.children.length; j++) {
-                                let newChildNode = nodeConfig.children[j];
-                                newChildNode.attribute_id = nodeConfig.children[j].id;
-                                newChildNode.id = parentTree+'_'+nodeConfig.children[j].id;
-                                newChildNode.li_attr = {
-                                    'class': nodeConfig.children[j].cls
-                                };
-                                treeInstance.create_node(parentTree, newChildNode, 'last');
-                            }
-                        }
-                    }
-
-                    treeDiv.on('select_node.jstree', function (e, data) {
-                        editSet.register(data.node);
-                    });
-
-                    treeDiv.on('copy_node.jstree', function (e, data) {
-                        let originalNode = data.original.original;
-                        let copiedNode = data.node.original;
-
-                        // Assign properties from the original node to the copied node
-                        Object.assign(copiedNode, {
-                            cls: originalNode.cls,
-                            is_user_defined: originalNode.is_user_defined,
-                            allowDrag: originalNode.allowDrag,
-                            allowDrop: originalNode.allowDrop,
-                            entity_id: originalNode.entity_id,
-                            attribute_id: originalNode.attribute_id
-                        });
-
-                        treeDiv1.jstree(true).redraw(copiedNode);
-                        treeInstance.open_node(data.node.parent);
-
-                        let treeIns = treeDiv2.jstree(true);
-                        let rootNode = treeIns.get_node('#');
-
-                        if (rootNode.children.length === 1) {
-                            editSet.rightAppend(treeIns);
-                        } else if(rootNode.children.length > 1) {
-                            editSet.rightRemove(treeIns);
-                        }
-                    });
-
-                    treeDiv.on('dblclick.jstree', function (event) {
-                        const selectedNode = treeInstance.get_selected(true)[0];
-                        if (selectedNode && selectedNode.original.cls === 'folder') {
-                            editSet.validateGroupName(selectedNode.text, selectedNode.id);
-                            treeInstance.edit(selectedNode);
-                        }
-                        treeInstance.open_all();
-                    });
-
-                    treeInstance.open_all();
+        $groupNotSelectedAlert = $escaper->escapeJs(__('Please select a node.'));
+        $groupNameAlreadyExistsAlert = $escaper->escapeJs(__('An attribute group named "/name/" already exists.'));
+        $emptyText = $escaper->escapeJs(__('Empty'));
+        $configJson = <<<JSON
+{
+    "saveUrl": "$saveUrl",
+    "readOnly": $readOnly,
+    "tree": {
+        "assigned": {
+            "selector": "#tree-div1",
+            "placeholder": false,
+            "data": $groupTree
+        },
+        "unassigned": {
+            "selector": "#tree-div2",
+            "placeholder": {
+                "text": "$emptyText"
+            },
+            "data": $attributeTreeJson
+        }
+    },
+    "newGroupModal": {
+        "title": "$newGroupModalTitle",
+        "content": "$newGroupModalContent"
+    },
+    "errors": {
+        "save": {
+            "message": "$cannotSaveAlert"
+        },
+        "unassign": {
+            "message": "$cannotUnassignAttributeAlert"
+        },
+        "delete_group": {
+            "message": "$cannotDeleteGroupAlert"
+        },
+        "group_not_selected": {
+            "message": "$groupNotSelectedAlert"
+        },
+        "validation": {
+            "isUnique": {
+                "message": "$groupNameAlreadyExistsAlert",
+                "bindings": {
+                    "/name/": "value"
                 }
-
-                function checkCallback(operation, node, parent, position) {
-                    if (operation === 'move_node') {
-                        if(parent.original && !parent.original.allowDrop) {
-                            return false;
-                        }
-
-                        if((node.original.cls === 'system-leaf' || node.original.cls === 'leaf')
-                            && parent.text === 'ROOT') {
-                            return false;
-                        }
-
-                        if (node.original.cls === 'folder' && parent.original.cls === 'folder'
-                            && parent.id !== '#') {
-                            return false;
-                        }
-                        return true;
-                    }
-                    if (operation === 'copy_node' && (node.original.is_unassignable == 0
-                        && node.original.is_user_defined == 0)) {
-                        alert({
-                            content: '{$block->escapeJs(
-            __('You can\'t remove attributes from this attribute set.')
-        )}'
-                        });
-                        return false;
-                    }
-                    if (operation === 'rename_node') {
-                        return editSet.validateGroupName(position, node.id);
-                    }
-
-                    return true;
-                }
-
-                editSet = function () {
-                    return {
-                        register: function (node) {
-                            editSet.currentNode = node;
-                        },
-
-                        submit: function () {
-                            var i, child, newNode;
-                            var rootNode = treeDiv1.jstree(true).get_node('#');
-                            if (rootNode.children.length === 0) {
-                                return;
-                            }
-
-                            if (editSet.SystemNodesExists(editSet.currentNode)) {
-                                alert({
-                                    content: '{$systemAttributeWarning}'
-                                });
-                                return;
-                            }
-
-                            if (editSet.currentNode && editSet.currentNode.original.cls === "folder") {
-                                let currentChild = editSet.currentNode.children;
-                                for (i = 0; i < currentChild.length; i++) {
-                                    let child = treeDiv1.jstree(true).get_node(currentChild[i]);
-                                    if (child.original.is_user_defined == 1) {
-                                        newNode = child.original;
-                                        treeDiv2.jstree(true).create_node('#', newNode, 'last');
-                                    }
-                                }
-
-                                let currentNodeId = editSet.currentNode.id.replace(editSet.currentNode.parent+'_', '');
-                                editSet.req.removeGroups[currentNodeId] = currentNodeId;
-                                treeDiv1.jstree(true).delete_node(editSet.currentNode);
-                                editSet.currentNode = false;
-                            }
-                        },
-
-                        SystemNodesExists: function (currentNode) {
-                            if (!currentNode) {
-                                alert({
-                                    content: '{$block->escapeJs(__('Please select a node.'))}'
-                                });
-                                return;
-                            }
-
-                            let children = currentNode.children;
-                            for (let i = 0; i < children.length; i++) {
-                                let child = treeDiv1.jstree(true).get_node(children[i]);
-                                if (child.original.is_unassignable != 1) {
-                                    return true;
-                                }
-                            }
-                        },
-
-                        addGroup: function () {
-                            prompt({
-                                title: "{$block->escapeJs($block->escapeHtml(__('Add New Group')))}",
-                                content: "{$block->escapeJs($block->escapeHtml(__('Please enter a new group name.')))}",
-                                value: "",
-                                validation: true,
-                                validationRules: ['required-entry'],
-                                attributesForm: {
-                                    novalidate: 'novalidate',
-                                    action: ''
-                                },
-                                attributesField: {
-                                    name: 'name',
-                                    'data-validate': '{required:true}',
-                                    maxlength: '255'
-                                },
-                                actions: {
-                                    confirm: function (group_name) {
-                                        group_name = group_name.strip();
-
-                                        if (!editSet.validateGroupName(group_name, 0)) {
-                                            return;
-                                        }
-
-                                        var newNodeData = {
-                                            text: group_name,
-                                            icon: 'jstree-folder',
-                                            cls: 'folder',
-                                            allowDrop: true,
-                                            allowDrag: true
-                                        };
-
-                                        let rootNode = treeDiv1.jstree(true).get_node('#');
-                                        treeDiv1.jstree(true).create_node(rootNode, newNodeData, 'last');
-                                    }
-                                }
-                            });
-                        },
-
-                        validateGroupName : function(name, exceptNodeId) {
-                            name = name.trim();
-                            var result = true;
-                            if (name === '') {
-                                result = false;
-                            }
-                            var rootNode = treeDiv1.jstree(true).get_node('#');
-                            for (var i = 0; i < rootNode.children.length; i++) {
-                                var childNode = treeDiv1.jstree(true).get_node(rootNode.children[i]);
-                                if (childNode.text.toLowerCase() === name.toLowerCase()
-                                    && childNode.id !== exceptNodeId) {
-                                    errorText = '{$block->escapeJs(
-            __('An attribute group named "/name/" already exists.')
-        )}';
-                                    alert({
-                                        content: errorText.replace("/name/",name)
-                                    });
-                                    result = false;
-                                }
-                            }
-                            return result;
-                        },
-
-                        save: function () {
-                            var block;
-
-                            if ($('messages')) {
-                                $('messages').update();
-                            } else {
-                                block = jQuery('<div/>').attr('id', 'messages');
-                                jQuery('.page-main-actions').after(block);
-                            }
-                            TreePanels.rebuildTrees();
-                            if(!jQuery('#set-prop-form').valid()) {
-                                return;
-                            }
-                            editSet.req.attribute_set_name = $('attribute_set_name').value;
-                            if (!editSet.req.form_key) {
-                                editSet.req.form_key = FORM_KEY;
-                            }
-
-                            for (var key in editSet.req) {
-                                if (Array.isArray(editSet.req[key])) {
-                                    editSet.req[key] = editSet.req[key].filter(item => item !== null);
-                                }
-                            }
-
-                            var reqData = { data: JSON.stringify(editSet.req) };
-                            jQuery.ajax({
-                                url: '{$block->escapeJs($block->getMoveUrl())}',
-                                type: 'POST',
-                                data: reqData,
-                                success: editSet.success,
-                                error: editSet.failure
-                            });
-                        },
-
-                        success: function (response) {
-                            if (response.error || response.message) {
-                                jQuery("#messages").html(response.message);
-                            } else if (response.ajaxExpired && response.ajaxRedirect) {
-                                setLocation(response.ajaxRedirect);
-                            } else if (response.url) {
-                                setLocation(response.url);
-                            }
-                        },
-
-                        failure: function(o) {
-                            alert({
-                                content: '{$block->escapeJs(__('Sorry, we\'re unable to complete this request.'))}'
-                            });
-                        },
-
-                        rightAppend: function(treeInstance) {
-                            let newNode = {
-                                text: 'Empty',
-                                id: '#_empty',
-                                cls: 'folder',
-                                is_user_defined: 1,
-                                allowDrop: false,
-                                allowDrag: false
-                            };
-                            treeInstance.create_node('#', newNode, 'last');
-                        },
-
-                        rightRemove: function(treeInstance) {
-                            var emptyNode = treeInstance.get_node('#_empty');
-
-                            if (emptyNode) {
-                                treeInstance.delete_node(emptyNode);
-                            }
-                        },
-
-                        rightBeforeAppend: function(tree, nodeThis, node, newParent) {
-                            return original(tree, nodeThis, node, newParent);
-                        },
-
-                        rightBeforeInsert: function(tree, nodeThis, node, newParent) {
-                            return original(tree, nodeThis, node, newParent);
-                        }
-                    }
-                }();
-
-                function initVars() {
-                    editSet.req = {};
-                    editSet.req.attributes = false;
-                    editSet.req.groups = new Array();
-                    editSet.req.not_attributes = false;
-                    editSet.req.attribute_set_name = false;
-                    editSet.req.removeGroups = new Array();
-                }
-
-                jQuery(function() {
-                    initVars();
-                    TreePanels.init();
-                    jQuery("[data-role='spinner']").hide();
-                });
-
-            });
-            require(['tree-panel']);
-script;
+            }
+        }
+    }
+}
+JSON;
+
+        $scriptString = <<<JS
+define(
+    'tree-panel',
+    ['jquery', 'Magento_Catalog/js/product/set/editor', 'prototype', 'domReady!'],
+    function(jQuery, Editor) {
+        window.editSet = new Editor($configJson);
+
+        window.editSet.beforeSave = window.editSet.beforeSave.wrap(function(_super) {
+            if ($('messages')) {
+                $('messages').update();
+            } else {
+                jQuery('.page-main-actions').after(jQuery('<div/>').attr('id', 'messages'));
+            }
+            if(!jQuery('#set-prop-form').valid()) {
+                return;
+            }
+            this.req.attribute_set_name = $('attribute_set_name').value;
+            if (!this.req.form_key && window.FORM_KEY) {
+                this.req.form_key = window.FORM_KEY;
+            }
+            _super();
+        });
+
+        window.editSet.afterSave = window.editSet.afterSave.wrap(function(_super, response) {
+            if (response.error || response.message) {
+                jQuery('#messages').html(response.message);
+            } else {
+                _super(response);
+            }
+        });
+
+        jQuery("[data-role='spinner']").hide();
+
+        return window.editSet;
+    }
+);
+require(['tree-panel']);
+JS;
         ?>
         <?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?>
     </div>
diff --git a/vendor/magento/module-catalog/view/adminhtml/web/js/product/set/editor.js b/vendor/magento/module-catalog/view/adminhtml/web/js/product/set/editor.js
new file mode 100644
index 0000000000000..c40d2d17771a4
--- /dev/null
+++ b/vendor/magento/module-catalog/view/adminhtml/web/js/product/set/editor.js
@@ -0,0 +1,1038 @@
+/**
+ * Copyright 2025 Adobe
+ * All Rights Reserved.
+ */
+define([
+    'jquery',
+    'underscore',
+    'Magento_Ui/js/modal/prompt',
+    'Magento_Ui/js/modal/alert',
+    'jquery/jstree/jquery.jstree'
+], function ($, _, prompt, alert) {
+    'use strict';
+
+    /**
+     * Create node object
+     *
+     * @param {Object} data
+     * @param {Object} [instance=null]
+     * @return {Object}
+     * @private
+     */
+    function createNode(data, instance) {
+        const node = instance || {},
+            isFalsy = (value) => ['0', 0, false].indexOf(value) !== -1;
+
+        Object.assign(
+            node,
+            {
+                text: null,
+                entity_id: null,
+                cls: 'leaf',
+                allowDrag: false,
+                allowDrop: false,
+                is_user_defined: false,
+                is_unassignable: false
+            },
+            data,
+            {
+                allowDrag: !isFalsy(data.allowDrag),
+                allowDrop: !isFalsy(data.allowDrop),
+                is_user_defined: !isFalsy(data.is_user_defined),
+                is_unassignable: !isFalsy(data.is_unassignable)
+            }
+        );
+        node._id = data._id || data.id;
+        node.li_attr = {class: node.cls};
+        node.state = {opened: true, ...node.state || {}};
+        if (Array.isArray(node.children)) {
+            node.children = node.children.map((child) => createNode(child));
+        }
+        // A unique ID will be generated by jstree
+        delete node.id;
+        if (node._id === true) {
+            node._id = null;
+        }
+        return node;
+    }
+
+    /**
+     * @param {Tree} tree
+     * @param {Object} config
+     * @return {Object}
+     * @private
+     */
+    function _Tree(tree, config) {
+        const settings = {core: {}, ...config.settings || {}},
+            readOnly = config.readOnly || false,
+            events = config.events || {trigger: () => !readOnly},
+            selector = config.selector,
+            validations = config.validations || {},
+            initialData = (config.data || []).map((node) => createNode(node)),
+            placeholder = config.placeholder ? createNode({
+                id: 'empty',
+                text: 'Empty',
+                cls: 'folder',
+                allowDrop: false,
+                allowDrag: false,
+                ..._.isObject(config.placeholder) ? config.placeholder : {},
+                ...initialData.length === 1 && initialData[0]._id === 'empty' ? initialData[0] : {}
+            }) : null;
+
+        /**
+         * Validate value against rule
+         *
+         * @param {String} rule
+         * @param {String} value
+         * @param {Function} [callback=null]
+         * @return {Boolean}
+         */
+        function validate(rule, value, callback) {
+            const validators = {
+                isNotEmpty: () => !!value,
+                isUnique: () => {
+                    const rootNode = tree.root();
+                    let result = true;
+
+                    for (let i = 0; i < rootNode.children.length; i++) {
+                        let node = tree.get(rootNode.children[i]);
+
+                        if (node.text.toLowerCase() === value.toLowerCase() && (!callback || callback(node))) {
+                            result = false;
+                            break;
+                        }
+                    }
+                    return result;
+                }
+            };
+
+            return validators[rule]();
+        }
+
+        /**
+         * Validate node property
+         *
+         * @param {String} property
+         * @param {String} value
+         * @param {Function} [callback=null]
+         * @return {Boolean}
+         */
+        function isValid(property, value, callback) {
+            if (!validations[property] || validations[property].length < 1) {
+                return true;
+            }
+
+            value = value.trim();
+
+            const result = {property, value, code: null, isValid: true};
+
+            for (let i = 0; i < validations[property].length; i++) {
+                if (!validate(validations[property][i], value, callback)) {
+                    result.code = validations[property][i];
+                    result.isValid = false;
+                    break;
+                }
+            }
+            return (events.trigger('afterValidate', {tree, result}) || result).isValid;
+        }
+
+        /**
+         * Check if node is draggable
+         *
+         * @param {Object[]} nodes
+         * @return {Boolean}
+         */
+        function isDraggable(nodes) {
+            for (let i = 0; i < nodes.length; i++) {
+                if (!events.trigger('beforeDrag', {tree, node: nodes[i]})) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Check if operation is allowed
+         *
+         * @param {String} operation
+         * @param {Object} node
+         * @param {Object} parent
+         * @param {Number|String} position
+         * @param {Object} more
+         * @return {Boolean}
+         */
+        function check(operation, node, parent, position, more) {
+            let result;
+
+            switch (operation) {
+            case 'move_node':
+                result = events.trigger('beforeMove', {tree, node, parent, position, more});
+                break;
+            case 'copy_node':
+                result = events.trigger('beforeCopy', {tree, node, parent, position, more});
+                break;
+            case 'create_node':
+                createNode({...node}, node);
+                result = placeholder && placeholder._id === node._id && (node.id = node._id)
+                    || events.trigger('beforeCreate', {tree, node, parent, position, more});
+                if (result) {
+                    result = isValid('text', node.text);
+                }
+                break;
+            case 'delete_node':
+                result = events.trigger('beforeDelete', {tree, node, parent, position, more});
+                break;
+            case 'edit':
+                position = node.text;
+                // eslint-disable-next-line no-fallthrough
+            case 'rename_node':
+                result = events.trigger('beforeRename', {tree, node, parent, name: position, more});
+                if (result) {
+                    result = isValid('text', position, (item) => item.id !== node.id);
+                }
+                break;
+            default:
+                result = true;
+            }
+
+            return result;
+        }
+
+        /**
+         * Register listeners
+         */
+        function registerListeners() {
+            const jElem = $(selector);
+
+            jElem.on('copy_node.jstree', function (e, data) {
+                data.node.original = data.original.original;
+                if (placeholder && tree.root().children.length > 0) {
+                    let node = tree.get(placeholder._id);
+
+                    if (node) {
+                        tree.delete(node);
+                    }
+                }
+                events.trigger('afterCopy', {tree, ...data});
+            });
+
+            jElem.on('delete_node.jstree', function (e, data) {
+                if (placeholder
+                    && tree.root().children.length === 0
+                    && !tree.get(placeholder._id)
+                ) {
+                    tree.append({...placeholder, id: placeholder._id});
+                }
+                events.trigger('afterDelete', {tree, ...data});
+            });
+
+            jElem.on('dblclick.jstree', function () {
+                const selectedNode = tree.getSelected();
+
+                if (selectedNode && tree.isGroup(selectedNode)) {
+                    tree.edit(selectedNode);
+                }
+            });
+
+            jElem.on('create_node.jstree', function (e, data) {
+                events.trigger('afterCreate', {tree, ...data});
+            });
+
+            jElem.on('move_node.jstree', function (e, data) {
+                events.trigger('afterMove', {tree, ...data});
+            });
+
+            jElem.on('rename_node.jstree', function (e, data) {
+                events.trigger('afterRename', {tree, ...data});
+            });
+        }
+
+        if (placeholder
+            && (initialData.length === 0 || initialData.length === 1 && initialData[0]._id === placeholder._id)
+        ) {
+            initialData[0] = {...placeholder, id: placeholder._id};
+        }
+
+        registerListeners();
+
+        settings.core = {
+            ...settings.core || {},
+            data: initialData,
+            dblclick_toggle: false,
+            check_callback: readOnly ? false : check
+        };
+        settings.dnd = {
+            inside_pos: 'last',
+            ...settings.dnd || {},
+            is_draggable: readOnly ? false : isDraggable
+        };
+        settings.plugins = [
+            ...settings.plugins || [],
+            'dnd'
+        ];
+
+        return $(selector).jstree(settings).jstree(true);
+    }
+
+    /**
+     * Tree class
+     *
+     * @param {Object} config
+     * @constructor
+     */
+    function Tree(config) {
+        this.instance = _Tree(this, config);
+    }
+
+    /**
+     * Get root node
+     *
+     * @return {Object}
+     */
+    Tree.prototype.root = function () {
+        return this.get($.jstree.root);
+    };
+
+    /**
+     * Get node
+     *
+     * @param {Object|String} node
+     * @return {Object}
+     */
+    Tree.prototype.get = function (node) {
+        return this.instance.get_node(node);
+    };
+
+    /**
+     * Get node parent
+     *
+     * @param {Object|String} node
+     * @return {Object|null}
+     */
+    Tree.prototype.parent = function (node) {
+        const parent = this.instance.get_parent(node);
+
+        return parent ? this.get(parent) : null;
+    };
+
+    /**
+     * Append node to the tree
+     *
+     * @param {Object} data
+     * @param {Object|String|null} [parent=null]
+     * @return {String|Boolean} - the ID of the newly create node
+     */
+    Tree.prototype.append = function (data, parent) {
+        return this.create(data, parent, 'last');
+    };
+
+    /**
+     * Prepend node to the tree
+     *
+     * @param {Object} data
+     * @param {Object|String|null} [parent=null]
+     * @return {String|Boolean} - the ID of the newly create node
+     */
+    Tree.prototype.prepend = function (data, parent) {
+        return this.create(data, parent, 'first');
+    };
+
+    /**
+     * Prepend node to the tree
+     *
+     * @param {Object} data
+     * @param {Object|String|null} [parent=null]
+     * @param {String|Number|null} [position=null] - 'first', 'last', 'before', 'after', 'inside', 0, 1, 2, ...
+     * @return {String|Boolean} - the ID of the newly create node
+     */
+    Tree.prototype.create = function (data, parent, position) {
+        position = !_.isUndefined(position) ? position : 'last';
+        return this.instance.create_node(parent ? this.get(parent) : null, data, position);
+    };
+
+    /**
+     * Copy nodes into the tree in specified position
+     *
+     * @param {Object[]} nodes
+     * @param {Object|String|null} [parent=null]
+     * @param {String|Number|null} [position=null] - 'first', 'last', 'before', 'after', 'inside', 0, 1, 2, ...
+     * @return {String|Boolean}
+     */
+    Tree.prototype.copy = function (nodes, parent, position) {
+        position = !_.isUndefined(position) ? position : 'last';
+        return this.instance.copy_node(nodes, parent ? parent : this.root(), position);
+    };
+
+    /**
+     * Move nodes in specified position
+     *
+     * @param {Object[]} nodes
+     * @param {Object|String|null} [parent=null]
+     * @param {String|Number|null} [position=null] - 'first', 'last', 'before', 'after', 'inside', 0, 1, 2, ...
+     * @return {String|Boolean}
+     */
+    Tree.prototype.move = function (nodes, parent, position) {
+        position = !_.isUndefined(position) ? position : 'last';
+        return this.instance.move_node(nodes, parent ? parent : this.root(), position);
+    };
+
+    /**
+     * Delete node from the tree
+     *
+     * @param {Object|String} node
+     * @return {Boolean}
+     */
+    Tree.prototype.delete = function (node) {
+        return this.instance.delete_node(node);
+    };
+
+    /**
+     * Rename node
+     *
+     * @param {Object|String} node
+     * @param {String|null} [text=null]
+     * @return {Boolean}
+     */
+    Tree.prototype.rename = function (node, text) {
+        return this.instance.rename_node(node, text);
+    };
+
+    /**
+     * Edit node
+     *
+     * @param {Object|String} node
+     * @param {String|null} [text=null]
+     */
+    Tree.prototype.edit = function (node, text) {
+        this.instance.edit(node, text);
+    };
+
+    /**
+     * Expand node
+     *
+     * @param {Object|String} node
+     */
+    Tree.prototype.open = function (node) {
+        this.instance.open_node(node);
+    };
+
+    /**
+     * Get selected node
+     *
+     * @return {Object}
+     */
+    Tree.prototype.getSelected = function () {
+        return this.instance.get_selected(true)[0];
+    };
+
+    /**
+     * Get original node object
+     *
+     * @param {Object|String} node
+     * @return {Object}
+     */
+    Tree.prototype.getOriginalNode = function (node) {
+        let nodeObj = _.isObject(node) ? node : this.get(node);
+
+        return this.isRoot(node)
+            ? null
+            : nodeObj.original || nodeObj;
+    };
+
+    /**
+     * Check if node is root
+     *
+     * @param {Object|String} node
+     * @return {Boolean}
+     */
+    Tree.prototype.isRoot = function (node) {
+        let nodeObj = _.isObject(node) ? node : this.get(node);
+
+        return nodeObj.id === $.jstree.root;
+    };
+
+    /**
+     * Check if node is a group
+     *
+     * @param {Object|String} node
+     * @return {Boolean}
+     */
+    Tree.prototype.isGroup = function (node) {
+        let nodeObj = _.isObject(node) ? node : this.get(node);
+
+        return !this.isRoot(nodeObj) && this.getOriginalNode(nodeObj).cls === 'folder';
+    };
+
+    /**
+     * Find the first child with given parent node and matching a set of property and value pairs
+     *
+     * @param {Object|String|null} [parent=null]
+     * @param {Object[]|null} [conditions=null]
+     * @param {Boolean|null} [recursive=null]
+     * @return {Object}
+     */
+    Tree.prototype.find = function (parent, conditions, recursive) {
+        const _self = this,
+            parentNode = parent ? _self.get(parent) : _self.root();
+
+        recursive = recursive || false;
+        conditions = conditions || [];
+        conditions = _.isArray(conditions) ? conditions : [conditions];
+        for (let i = 0; i < parentNode.children.length; i++) {
+            let childNode = _self.get(parentNode.children[i]);
+
+            if (!conditions.length
+                || conditions.some((condition) => _.isMatch(this.getOriginalNode(childNode), condition))
+                || recursive && (childNode = _self.find(childNode, conditions, recursive))
+            ) {
+                return childNode;
+            }
+        }
+        return null;
+    };
+
+    /**
+     * Find all children with given parent node and matching a set of property and value pairs
+     *
+     * @param {Object|String|null} [parent=null]
+     * @param {Object[]|null} [conditions=null] - array of conditions to match. OR logic is applied to conditions
+     * @param {Boolean|null} [recursive=null]
+     * @return {Object[]}
+     */
+    Tree.prototype.findAll = function (parent, conditions, recursive) {
+        const _self = this,
+            parentNode = parent ? _self.get(parent) : _self.root(),
+            children = [];
+
+        recursive = recursive || false;
+        conditions = conditions || [];
+        conditions = _.isArray(conditions) ? conditions : [conditions];
+        for (let i = 0; i < parentNode.children.length; i++) {
+            let childNode = _self.get(parentNode.children[i]);
+
+            if (!conditions.length
+                || conditions.some((condition) => _.isMatch(this.getOriginalNode(childNode), condition))
+            ) {
+                children.push(_self.get(childNode));
+            }
+
+            if (recursive) {
+                children.push(..._self.findAll(childNode, conditions, recursive));
+            }
+        }
+
+        return children;
+    };
+
+    /**
+     * Editor class
+     *
+     * @param {Object} [config=null]
+     * @return {Editor}
+     * @constructor
+     */
+    function Editor(config) {
+        if (!(this instanceof Editor)) {
+            return new Editor(config);
+        }
+
+        config = config || {
+            readOnly: false,
+            tree: {
+                assigned: {},
+                unassigned: {}
+            }
+        };
+
+        const events = {
+            trigger: config.readOnly ? () => false : (name, data) => this[name](data.tree, data)
+        };
+
+        this.saveUrl = config.saveUrl;
+
+        this.errors = {
+            save: {
+                message: 'An error occurred while saving changes.'
+            },
+            unassign: {
+                message: 'This attribute cannot be unassigned.'
+            },
+            delete_group: {
+                message: 'This group cannot be deleted.'
+            },
+            group_not_selected: {
+                message: 'Please select a group.'
+            },
+            validation: {
+                isNotEmpty: {
+                    message: ':text cannot be empty.',
+                    bindings: {
+                        ':text': 'property'
+                    }
+                },
+                isUnique: {
+                    message: ':value is already in use.',
+                    bindings: {
+                        ':value': 'value'
+                    }
+                }
+            },
+            ...config.errors || {}
+        };
+
+        this.newGroupModal = {
+            title: '',
+            content: '',
+            ...config.newGroupModal || {}
+        };
+
+        this.assigned = new Tree({
+            events,
+            readOnly: config.readOnly,
+            settings: {
+                core: {
+                    animation: false
+                }
+            },
+            validations: {
+                text: ['isNotEmpty', 'isUnique']
+            },
+            ...config.tree.assigned
+        });
+
+        this.unassigned = new Tree({
+            events,
+            readOnly: config.readOnly,
+            settings: {
+                core: {
+                    themes: {
+                        dots: false
+                    }
+                }
+            },
+            ...config.tree.unassigned
+        });
+
+        this.req = {
+            attributes: [],
+            groups: [],
+            not_attributes: [],
+            removeGroups: []
+        };
+
+        if (config.readOnly) {
+            this.submit = function () {
+                this.error(Editor.ERRORS.READ_ONLY);
+            };
+            this.save = function () {
+                this.error(Editor.ERRORS.READ_ONLY);
+            };
+            this.addGroup = function () {
+                this.error(Editor.ERRORS.READ_ONLY);
+            };
+        }
+
+        return this;
+    }
+
+    /**
+     * Tree constructor
+     *
+     * @type {Function}
+     * @static
+     */
+    Editor.Tree = Tree;
+
+    /**
+     * @type {Object}
+     * @static
+     */
+    Editor.ERRORS = {
+        READ_ONLY: 'read_only',
+        SAVE: 'save',
+        VALIDATION: 'validation',
+        DELETE_GROUP: 'delete_group',
+        UNASSIGN: 'unassign',
+        GROUP_NOT_SELECTED: 'group_not_selected'
+    };
+
+    /**
+     * Callback that is called to check if to node can be dragged
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeDrag = function (tree, data) {
+        return tree.getOriginalNode(data.node).allowDrag;
+    };
+
+    /**
+     * Callback that is called to check if node can be moved
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeMove = function (tree, data) {
+        if (tree === this.unassigned) {
+            return tree.isRoot(data.parent) && !tree.isGroup(data.node);
+        }
+
+        return tree === this.assigned
+            && (
+                tree.isGroup(data.node)
+                && tree.isRoot(data.parent)
+                || !tree.isGroup(data.node)
+                && tree.isGroup(data.parent)
+                && tree.getOriginalNode(data.parent).allowDrop
+            );
+    };
+
+    /**
+     * Callback that is called to check if node can be moved to another tree
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeCopy = function (tree, data) {
+        if (tree === this.unassigned) {
+            return this.beforeUnassign(tree, data);
+        }
+        return this.beforeAssign(tree, data);
+    };
+
+    /**
+     * Callback that is called to check if node can be moved to "assigned" tree
+     *
+     * @param {Tree} tree destination tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeAssign = function (tree, data) {
+        return tree.isGroup(data.node)
+            && tree.isRoot(data.parent)
+            || !tree.isGroup(data.node)
+            && tree.isGroup(data.parent)
+            && tree.getOriginalNode(data.parent).allowDrop;
+    };
+
+    /**
+     * Callback that is called to check if node can be moved to "unassigned" tree
+     *
+     * @param {Tree} tree destination tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeUnassign = function (tree, data) {
+        let result;
+
+        if (tree.isGroup(data.node)) {
+            result = false;
+        } else if (tree.getOriginalNode(data.node).is_unassignable === false) {
+            this.error(Editor.ERRORS.UNASSIGN, {tree: this.assigned, node: data.node});
+            result = false;
+        } else {
+            result = tree.isRoot(data.parent);
+        }
+        return result;
+    };
+
+    /**
+     * Callback that is called to check if node can be renamed
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeRename = function (tree, data) {
+        return tree === this.assigned && tree.isGroup(data.node);
+    };
+
+    /**
+     * Callback that is called to check if node can be deleted
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeDelete = function (tree, data) {
+        let result;
+
+        if (tree === this.assigned) {
+            if (tree.isGroup(data.node) && this.assigned.find(data.node, [{is_unassignable: false}])) {
+                this.error(Editor.ERRORS.DELETE_GROUP, {node: data.node, tree});
+                result = false;
+            } else {
+                result = tree.isGroup(data.node) || tree.getOriginalNode(data.node).is_unassignable !== false;
+            }
+        } else {
+            result = true;
+        }
+        return result;
+    };
+
+    /**
+     * Callback that is called to check if node can be created
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     * @return {Boolean}
+     */
+    Editor.prototype.beforeCreate = function (tree, data) {
+        return tree === this.assigned
+            && tree.isRoot(data.parent)
+            && tree.isGroup(data.node);
+    };
+
+    /**
+     * Callback that is called after node is moved to another tree
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     */
+    Editor.prototype.afterCopy = function (tree, data) {
+        if (tree.isGroup(data.parent)) {
+            tree.open(data.parent);
+        }
+    };
+
+    /**
+     * Callback that is called after node is deleted
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     */
+    Editor.prototype.afterDelete = function (tree, data) {
+        if (tree !== this.assigned || !tree.isGroup(data.node)) {
+            return;
+        }
+
+        const originalNode = tree.getOriginalNode(data.node);
+
+        if (originalNode._id) {
+            this.req.removeGroups.push(originalNode._id);
+        }
+
+        this.unassigned.copy(tree.findAll(data.node, [{is_user_defined: true}]));
+    };
+
+    /**
+     * Callback that is called after node name is validated
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     */
+    Editor.prototype.afterValidate = function (tree, data) {
+        if (!data.result.isValid) {
+            this.error(Editor.ERRORS.VALIDATION, {tree, result: data.result});
+        }
+        return data.result;
+    };
+
+    /**
+     * Callback that is called after node is created
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     */
+    // eslint-disable-next-line no-unused-vars
+    Editor.prototype.afterCreate = function (tree, data) {};
+
+    /**
+     * Callback that is called after node is renamed
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     */
+    // eslint-disable-next-line no-unused-vars
+    Editor.prototype.afterRename = function (tree, data) {};
+
+    /**
+     * Callback that is called after node is moved
+     *
+     * @param {Tree} tree
+     * @param {Object} data
+     */
+    // eslint-disable-next-line no-unused-vars
+    Editor.prototype.afterMove = function (tree, data) {};
+
+    /**
+     * Callback that is called when an error occurs
+     *
+     * @param {string} code
+     * @param {Object} data
+     */
+    // eslint-disable-next-line no-unused-vars
+    Editor.prototype.error = function (code, data) {
+        data = data || {};
+        switch (code) {
+        case Editor.ERRORS.SAVE:
+            alert({content: data.message || this.errors.save.message});
+            break;
+        case Editor.ERRORS.VALIDATION:
+            let message = data.result.message;
+
+            if (!message && this.errors.validation[data.result.code]) {
+                message = this.errors.validation[data.result.code].message;
+
+                _.each(this.errors.validation[data.result.code].bindings || {}, function (value, key) {
+                    message = message.replace(key, data.result[value]);
+                });
+            }
+            message && alert({content: message});
+            break;
+        case Editor.ERRORS.DELETE_GROUP:
+            alert({content: data.message || this.errors.delete_group.message});
+            break;
+        case Editor.ERRORS.UNASSIGN:
+            alert({content: data.message || this.errors.unassign.message});
+            break;
+        case Editor.ERRORS.GROUP_NOT_SELECTED:
+            alert({content: data.message || this.errors.group_not_selected.message});
+            break;
+        }
+    };
+
+    /**
+     * Callback that is called after save fails
+     */
+    Editor.prototype.failure = function () {
+        this.error(Editor.ERRORS.SAVE);
+    };
+
+    /**
+     * Delete selected node
+     */
+    Editor.prototype.submit = function () {
+        const node = this.assigned.getSelected();
+
+        if (!node) {
+            this.error(Editor.ERRORS.GROUP_NOT_SELECTED);
+            return;
+        }
+
+        if (!this.assigned.isGroup(node)) {
+            return;
+        }
+
+        this.assigned.delete(node);
+    };
+
+    /**
+     * Callback that is called before saving changes
+     */
+    Editor.prototype.beforeSave = function () {};
+
+    /**
+     * Callback that is called after save
+     *
+     * @param {Object} response
+     */
+    Editor.prototype.afterSave = function (response) {
+        if (response.error || response.message) {
+            this.error(Editor.ERRORS.SAVE, {message: response.message});
+        } else if (response.ajaxExpired && response.ajaxRedirect) {
+            window.setLocation(response.ajaxRedirect);
+        } else if (response.url) {
+            window.setLocation(response.url);
+        }
+    };
+
+    /**
+     * Save changes
+     */
+    Editor.prototype.save = function () {
+        const _self = this,
+            isAttributeAssigned = {},
+            assignedAttributes = [],
+            unassignedAttributes = [],
+            groups = [];
+
+        _self.assigned.findAll().forEach(function (groupNode, gIndex) {
+            let groupId = _self.assigned.getOriginalNode(groupNode)._id || groupNode.id;
+
+            groups.push([groupId, groupNode.text.trim(), gIndex + 1]);
+
+            _self.assigned.findAll(groupNode).forEach(function (attributeNode, aIndex) {
+                let originalNode = _self.assigned.getOriginalNode(attributeNode),
+                    attributeId = originalNode._id;
+
+                if (attributeId > 0 && isAttributeAssigned[attributeId] === undefined) {
+                    isAttributeAssigned[attributeId] = true;
+                    assignedAttributes.push([attributeId, groupId, aIndex + 1, originalNode.entity_id]);
+                }
+            });
+        });
+
+        _self.unassigned.findAll().forEach(function (attributeNode) {
+            let originalNode = _self.assigned.getOriginalNode(attributeNode),
+                attributeId = originalNode._id;
+
+            if (attributeId > 0 && originalNode.entity_id > 0) {
+                unassignedAttributes.push(originalNode.entity_id);
+            }
+        });
+
+        _self.req.groups = groups;
+        _self.req.attributes = assignedAttributes;
+        _self.req.not_attributes = unassignedAttributes;
+        _self.req.removeGroups = _.uniq(_self.req.removeGroups);
+
+        this.beforeSave();
+
+        $.ajax({
+            url: _self.saveUrl,
+            type: 'POST',
+            data: {data: JSON.stringify(_self.req)},
+            success: _self.afterSave.bind(_self),
+            error: _self.failure.bind(_self)
+        });
+    };
+
+    /**
+     * Add new group to the assigned tree
+     *
+     * @param {String} [name=null]
+     * @return {String}
+     */
+    Editor.prototype.addGroup = function (name) {
+        const _self = this,
+            add = (text) => _self.assigned.append({
+                text: text.trim(),
+                icon: 'jstree-folder',
+                cls: 'folder',
+                allowDrop: true,
+                allowDrag: true
+            });
+
+        if (name) {
+            return add(name);
+        }
+
+        prompt({
+            title: this.newGroupModal.title,
+            content: this.newGroupModal.content,
+            value: '',
+            validation: true,
+            validationRules: ['required-entry'],
+            attributesForm: {
+                novalidate: 'novalidate',
+                action: ''
+            },
+            attributesField: {
+                name: 'name',
+                'data-validate': '{required:true}',
+                maxlength: '255'
+            },
+            actions: {
+                confirm: function (group_name) {
+                    add(group_name);
+                }
+            }
+        });
+    };
+
+    return Editor;
+});
diff --git a/vendor/magento/module-configurable-product/view/adminhtml/templates/catalog/product/attribute/set/js.phtml b/vendor/magento/module-configurable-product/view/adminhtml/templates/catalog/product/attribute/set/js.phtml
index 272234a0ee074..fa888d623270f 100644
--- a/vendor/magento/module-configurable-product/view/adminhtml/templates/catalog/product/attribute/set/js.phtml
+++ b/vendor/magento/module-configurable-product/view/adminhtml/templates/catalog/product/attribute/set/js.phtml
@@ -1,11 +1,22 @@
 <?php
 /**
- *
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
+ * Copyright 2014 Adobe
+ * All Rights Reserved.
  */
 
 /** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
+/** @var \Magento\Framework\Escaper $escaper */
+
+$cannotDeleteGroupAlert = $escaper->escapeJs(
+    __(
+        'This group contains attributes used in configurable products. '
+        . 'Please move these attributes to another group and try again.'
+    )
+);
+
+$cannotUnassignAttributeAlert = $escaper->escapeJs(
+    __('This attribute is used in configurable products. You cannot remove it from the attribute set.')
+);
 ?>
 
 <?php $scriptString = <<<script
@@ -14,58 +25,29 @@ require([
     "Magento_Ui/js/modal/alert",
     "tree-panel"
 ], function(alert){
-ConfigurableNodeExists = function(currentNode) {
-    for (var i in currentNode.childNodes ) {
-        if (currentNode.childNodes[i].id) {
-            child = editSet.currentNode.childNodes[i];
-            if (child.attributes.is_configurable) {
-                return true;
-            }
-        }
-    }
-    return false;
-};
-
-editSet.submit = editSet.submit.wrap(function(original) {
-    if (editSet.currentNode){
-        if (ConfigurableNodeExists(editSet.currentNode)) {
-            alert({
-                content: '{$block->escapeJs(
-                    __('This group contains attributes used in configurable products. ' .
-                        'Please move these attributes to another group and try again.')
-                )}'
-            });
-            return;
-        }
-    }
-    return original();
-});
-
-editSet.rightBeforeAppend = editSet.rightBeforeAppend.wrap(function(original, tree, nodeThis, node, newParent) {
-    if (node.attributes.is_configurable == 1) {
+function check(tree, data) {
+    if (tree.isGroup(data.node) && tree.find(data.node, [{is_configurable: 1}])) {
         alert({
-            content: '{$block->escapeJs(
-                __('This attribute is used in configurable products. You cannot remove it from the attribute set.')
-            )}'
+            content: '$cannotDeleteGroupAlert'
         });
         return false;
-    }
-    return original(tree, nodeThis, node, newParent);
-});
-
-editSet.rightBeforeInsert = editSet.rightBeforeInsert.wrap(function(original, tree, nodeThis, node, newParent) {
-    if (node.attributes.is_configurable == 1) {
+    } else if (tree.getOriginalNode(data.node).is_configurable) {
         alert({
-            content: '{$block->escapeJs(
-                __('This attribute is used in configurable products. You cannot remove it from the attribute set.')
-            )}'
+            content: '$cannotUnassignAttributeAlert'
         });
         return false;
+    } else {
+        return true;
     }
-    return original(tree, nodeThis, node, newParent);
+}
+editSet.beforeUnassign = editSet.beforeUnassign.wrap(function(original, tree, data) {
+    return check.call(this, this.assigned, data) && original(tree, data);
+});
+editSet.beforeDelete = editSet.beforeDelete.wrap(function(original, tree, data) {
+    return check.call(this, tree, data) && original(tree, data);
 });
-
 });
 script;
 ?>
-<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false) ?>
+<?= /* @noEscape */
+$secureRenderer->renderTag('script', [], $scriptString, false) ?>
