Repository: nadar/quill-delta-parser (php)

Issue:
Support for checked/unchecked lists
**Describe the bug**
Quill provides a list type called "check". This type is adding a little square which can be checked or not ([Quill documentation](https://quilljs.com/docs/modules/toolbar)).

![Image](https://github.com/user-attachments/assets/7ec84ecd-a954-4088-8764-31b5a4060659)

When the render function is called, this error is raised : 

![Image](https://github.com/user-attachments/assets/1d15c16a-2744-4c81-ba00-e6787d67e2a4)

**The delta code which generates the Problem**

```
{"insert":"\nThis is a checked element"},{"attributes":{"list":"checked"},"insert":"\n"},{"insert":"This is an unchecked element"},{"attributes":{"list":"unchecked"},"insert":"\n"}
```

**The expected html output the delta should produce**

A list with checkboxes (checked or not).

```
<style>
    ul {
      list-style: none;
      padding: 0;
    }
    li {
      display: flex;
      align-items: center;
    }
</style>
  
  
<ul>
  <li>
    <input type="checkbox" checked disabled>
    <label>This is a checked element</label>
  </li>
  <li>
    <input type="checkbox" disabled>
    <label>This is an unchecked element</label>
  </li>
</ul>
```

![Image](https://github.com/user-attachments/assets/7e5a3e33-83be-405e-b676-d771adcc325c)


Pull request description:
Check lists (#96)
The current PR is related to the issue #96 where I was reporting a missing support for the "check" list type which creates "checked" and "unchecked" lists that were not handled resulting in an error.

Closes #96 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89e85ec..cc825d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
 
 This project adheres to [Semantic Versioning](http://semver.org/).
 
+## 3.5.0 (27. March 2025)
++ [#96](https://github.com/nadar/quill-delta-parser/issues/96) Addded HTML rendering for lists with type "checked" and "unchecked".
+
 ## 3.4.2 (5. April 2024)
 
 + [#87](https://github.com/nadar/quill-delta-parser/issues/87) Fixed a bug where a line break preceding a list containing inline attributes results in improper HTML formatting for next paragraphs.
diff --git a/output.php b/output.php
index 3ed0057..907966d 100644
--- a/output.php
+++ b/output.php
@@ -83,7 +83,7 @@ var editor = new Quill('#editor', {
             ['bold', 'italic', 'underline', 'strike', 'blockquote'],
             ['link'],
             [{ 'script': 'sub'}, { 'script': 'super' }],
-            [{ 'list': 'ordered'}, { 'list': 'bullet' }],
+            [{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
             ['image', 'video'],
             ['clean']
         ]
diff --git a/src/listener/Lists.php b/src/listener/Lists.php
index f55245e..8eb0c3f 100644
--- a/src/listener/Lists.php
+++ b/src/listener/Lists.php
@@ -31,6 +31,21 @@ class Lists extends BlockListener
      */
     public const LIST_TYPE_ORDERED = 'ordered';
 
+    /**
+     * @var string
+     */
+    public const LIST_TYPE_CHECKED = 'checked';
+
+    /**
+     * @var string
+     */
+    public const LIST_TYPE_UNCHECKED = 'unchecked';
+
+    /**
+     * @var string Class to apply on <ul> for checklists
+     */
+    public $checkListClass = 'list-unstyled';
+
     /**
      * {@inheritDoc}
      */
@@ -104,11 +119,19 @@ class Lists extends BlockListener
                 $isOpen = false;
             }
 
+            // defines whether this attribute list element is a checked/unchecked list or not.
+            $isCheck = in_array($this->getPickType($pick), [self::LIST_TYPE_CHECKED, self::LIST_TYPE_UNCHECKED], true);
+
             // create the opening OL/UL tag
             // opening tag process has been simplified, see https://github.com/nadar/quill-delta-parser/pull/33
             // and https://github.com/nadar/quill-delta-parser/issues/30
             if (!$isOpen) {
-                $output .= '<'.$this->getListAttribute($pick).'>'.PHP_EOL;
+                $output .= '<'.$this->getListAttribute($pick);
+                if ($isCheck) {
+                    $output .= ' class="'.$this->checkListClass.'"';
+                }
+
+                $output .= '>'.PHP_EOL;
                 $isOpen = true;
             }
 
@@ -126,7 +149,17 @@ class Lists extends BlockListener
                 $output .= '<li></li>';
             } else {
                 $output .= '<li>';
-                $output .= $buffer;
+
+                if ($isCheck) {
+                    $output .= '<input type="checkbox" disabled';
+                    if ($this->getPickType($pick) === self::LIST_TYPE_CHECKED) {
+                        $output .= ' checked';
+                    }
+
+                    $output .= '><label>'.$buffer.'</label>';
+                } else {
+                    $output .= $buffer;
+                }
 
                 if ($nextIndent > $pick->line->getAttribute('indent', 0)) {
                     $output .= '<'.$this->getListAttribute($pick).'>'.PHP_EOL;
@@ -165,18 +198,26 @@ class Lists extends BlockListener
      */
     protected function getListAttribute(Pick $pick)
     {
-        $optionValueType = $pick->optionValue('type');
-        $type = is_array($optionValueType) ? $optionValueType['type'] : $optionValueType;
+        $type = $this->getPickType($pick);
 
         if ($type === self::LIST_TYPE_ORDERED) {
             return 'ol';
         }
 
-        if ($type === self::LIST_TYPE_BULLET) {
+        if (in_array($type, [self::LIST_TYPE_BULLET, self::LIST_TYPE_CHECKED, self::LIST_TYPE_UNCHECKED], true)) {
             return 'ul';
         }
 
         // prevent html injection in case the attribute is user input
         throw new Exception('The provided list type "'.$type.'" is not a known list type (ordered or bullet).');
     }
+
+    /**
+     * Get the list type for the given value.
+     */
+    protected function getPickType(Pick $pick): string
+    {
+        $optionValueType = $pick->optionValue('type');
+        return is_array($optionValueType) ? $optionValueType['type'] : $optionValueType;
+    }
 }


Test patch:
diff --git a/tests/ListCheckTest.php b/tests/ListCheckTest.php
new file mode 100644
index 0000000..dc9be3e
--- /dev/null
+++ b/tests/ListCheckTest.php
@@ -0,0 +1,17 @@
+<?php
+namespace nadar\quill\tests;
+
+class ListCheckTest extends DeltaTestCase
+{
+    public $json = <<<'JSON'
+{"ops":[{"insert":"This is a checked element"},{"attributes":{"list":"checked"},"insert":"\n"},{"insert":"This is an unchecked element"},{"attributes":{"list":"unchecked"},"insert":"\n"}]}
+JSON;
+
+    public $html = <<<'EOT'
+<ul class="list-unstyled">
+<li><input type="checkbox" disabled checked><label>This is a checked element</label></li>
+<li><input type="checkbox" disabled><label>This is an unchecked element</label></li>
+</ul>
+
+EOT;
+}