From 6c6d6307982dacb6ddf36dd5a3756f167aee678c Mon Sep 17 00:00:00 2001
From: Johannes Raggam <thetetet@gmail.com>
Date: Sun, 5 Nov 2023 15:28:10 +0100
Subject: [PATCH] feat: Improve support for input elements outside form.

---
 src/core/testmutationobserverdestroy.html | 28 +++++++++++++++++++++++
 src/core/utils.js                         |  6 ++---
 src/lib/dependshandler.js                 |  8 +++----
 src/pat/auto-suggest/auto-suggest.js      |  2 +-
 src/pat/checklist/index-infinite.html     | 28 +++++++++++++++++++++++
 src/pat/close-panel/close-panel.js        |  2 +-
 src/pat/image-crop/image-crop.js          |  2 +-
 src/pat/inject/test.html                  | 15 ++++++++++++
 src/pat/push/tools/local-quaivedemo.sh    |  1 +
 src/pat/sortable/sortable.js              |  2 +-
 10 files changed, 82 insertions(+), 12 deletions(-)
 create mode 100644 src/core/testmutationobserverdestroy.html
 create mode 100644 src/pat/checklist/index-infinite.html
 create mode 100644 src/pat/inject/test.html
 create mode 100755 src/pat/push/tools/local-quaivedemo.sh

diff --git a/src/core/testmutationobserverdestroy.html b/src/core/testmutationobserverdestroy.html
new file mode 100644
index 000000000..232c2dc76
--- /dev/null
+++ b/src/core/testmutationobserverdestroy.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width" />
+    <title>test mutation observer destroy</title>
+    <link rel="stylesheet" href="/style/common.css" />
+    src="/bundle.min.js"></script> src="/bundle.min.js"></script>
+  </head>
+  <body>
+    <div id="el"><p></p></div>
+    src="/bundle.min.js"></script>>
+        const el = document.getElementById('el');
+        const observer = new MutationObserver((rec) => {
+          debugger;
+          console.log("mutation");
+        });
+        observer.observe(el, {
+          attributeFilter: ["class"],
+          childList: true,
+        });
+
+        //debugger;
+
+        //observer.disconnect();
+    </script>
+  </body>
+</html>
diff --git a/src/core/utils.js b/src/core/utils.js
index 4493b3503..8fa1baf53 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -494,6 +494,7 @@ function parseLength(length, reference_length = null) {
 
 // Return a jQuery object with elements related to an input element.
 function findRelatives(el) {
+    el = jqToNode(el);
     const $el = $(el);
     let $relatives = $(el);
     let $label = $();
@@ -505,10 +506,7 @@ function findRelatives(el) {
         $label = $(`label[for="${el.id}"]`);
     }
     if (!$label.length) {
-        let $form = $el.closest("form");
-        if (!$form.length) {
-            $form = $(document.body);
-        }
+        const $form = $(el.form || el.closest("form") || document.body);
         $label = $form.find(`label[for="${el.name}"]`);
     }
     $relatives = $relatives.add($label);
diff --git a/src/lib/dependshandler.js b/src/lib/dependshandler.js
index a67044adb..0b9796c97 100644
--- a/src/lib/dependshandler.js
+++ b/src/lib/dependshandler.js
@@ -1,11 +1,11 @@
 import $ from "jquery";
 import parser from "./depends_parse";
+import utils from "../core/utils";
 
 function DependsHandler($el, expression) {
-    var $context = $el.closest("form");
-    if (!$context.length) $context = $(document);
+    const el = utils.jqToNode($el);
     this.$el = $el;
-    this.$context = $context;
+    this.$context = $(el.form || el.closest("form") || document);
     this.ast = parser.parse(expression); // TODO: handle parse exceptions here
 }
 
@@ -53,7 +53,7 @@ DependsHandler.prototype = {
             case "NOT":
                 return !this._evaluate(node.children[0]);
             case "AND":
-                for (const child of node.children.length) {
+                for (const child of node.children) {
                     if (!this._evaluate(child)) {
                         return false;
                     }
diff --git a/src/pat/auto-suggest/auto-suggest.js b/src/pat/auto-suggest/auto-suggest.js
index 6548957bd..1f7524aa1 100644
--- a/src/pat/auto-suggest/auto-suggest.js
+++ b/src/pat/auto-suggest/auto-suggest.js
@@ -132,7 +132,7 @@ export default Base.extend({
 
         // Clear values on reset.
         events.add_event_listener(
-            this.el.closest("form"),
+            this.el.form || this.el.closest("form"), // TODO: `closest` necessary?
             "reset",
             "pat-auto-suggest--reset",
             () => this.$el.select2("val", "")
diff --git a/src/pat/checklist/index-infinite.html b/src/pat/checklist/index-infinite.html
new file mode 100644
index 000000000..b81cec020
--- /dev/null
+++ b/src/pat/checklist/index-infinite.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <script src="/bundle.min.js"></script>
+    <title>Infinite scrolling</title>
+  </head>
+  <body>
+
+    <form class="pat-checklist">
+      <label><input type="checkbox" class="toggle-all" />Toggle checkboxes</label><br>
+      <fieldset id="infinite-boxes">
+          <label><input type="checkbox" checked="checked" /> Option one</label><br>
+          <label><input type="checkbox" /> Option two</label><br>
+          <label><input type="checkbox" /> Option three</label><br>
+          <label><input type="checkbox" /> Option four</label><br>
+
+          <a
+              href="./index-infinite.html#infinite-boxes"
+              class="pat-inject"
+              data-pat-inject="trigger: autoload-visible; target:self::element"
+              >Loading...</a>
+
+      </fieldset>
+    </form>
+
+  </body>
+</html>
diff --git a/src/pat/close-panel/close-panel.js b/src/pat/close-panel/close-panel.js
index 2f8436ea4..5486f54b3 100644
--- a/src/pat/close-panel/close-panel.js
+++ b/src/pat/close-panel/close-panel.js
@@ -27,7 +27,7 @@ export default Base.extend({
             if (
                 e.target.matches(":not([formnovalidate])") &&
                 e.target.matches("[type=submit], button:not([type=button])") &&
-                this.el.closest("form")?.checkValidity() === false
+                this.el.form?.checkValidity() === false
             ) {
                 // Prevent closing an invalid form when submitting.
                 return;
diff --git a/src/pat/image-crop/image-crop.js b/src/pat/image-crop/image-crop.js
index a9f4be408..789eff90a 100644
--- a/src/pat/image-crop/image-crop.js
+++ b/src/pat/image-crop/image-crop.js
@@ -52,7 +52,7 @@ var _ = {
             // Set the form ID
             if (opts.formId.length === 0) {
                 // no form ID supplied. Look for the closest parent form element
-                data.form = $this.closest("form");
+                data.form = $(this.form || this.closest("form"));
                 if (data.form.length === 0) {
                     log.error("No form specified or found");
                     return;
diff --git a/src/pat/inject/test.html b/src/pat/inject/test.html
new file mode 100644
index 000000000..4b3cd6a68
--- /dev/null
+++ b/src/pat/inject/test.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width" />
+    <title>Test</title>
+    <link rel="stylesheet" href="/style/common.css" />
+    <script src="/bundle.min.js"></script>
+  </head>
+  <body>
+    <form class="pat-form pat-inject">
+      <button formaction="test.html" class="pat-button" type="submit">Save button</button>
+    </form>
+  </body>
+</html>
diff --git a/src/pat/push/tools/local-quaivedemo.sh b/src/pat/push/tools/local-quaivedemo.sh
new file mode 100755
index 000000000..76be4a028
--- /dev/null
+++ b/src/pat/push/tools/local-quaivedemo.sh
@@ -0,0 +1 @@
+./py/bin/python send.py quaivedemo push_marker message_counter
diff --git a/src/pat/sortable/sortable.js b/src/pat/sortable/sortable.js
index 2dc4c9c44..c87ed17ff 100644
--- a/src/pat/sortable/sortable.js
+++ b/src/pat/sortable/sortable.js
@@ -20,7 +20,7 @@ export default Base.extend({
         if (window.__patternslib_import_styles) {
             import("./_sortable.scss");
         }
-        this.$form = this.$el.closest("form");
+        this.$form = $(this.el.form || this.el.closest("form"));
         this.options = parser.parse(this.$el, false);
         this.recordPositions().initScrolling();
         this.$el.on("pat-update", this.onPatternUpdate.bind(this));