From ff5f0a0372160d76afd25b6d83e46c7417dd4db9 Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Thu, 10 Apr 2025 15:09:26 +0200
Subject: [PATCH 1/9] feature: toggle field

---
 changelog.txt              |  4 +++
 src/Field/ToggleField.php  | 14 ++++++++++
 templates/input-toggle.php | 56 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 74 insertions(+)
 create mode 100644 src/Field/ToggleField.php
 create mode 100644 templates/input-toggle.php

diff --git a/changelog.txt b/changelog.txt
index c21b67c..fd47444 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,9 @@
 # Changelog
 
+## [3.3.0] - 2025-04-XX
+### Added
+- ToggleField class, extends CheckboxField for toggle button functionality.
+
 ## [3.2.1] - 2024-10-30
 ### Changed
 - Raised required PHP version to 7.4.
diff --git a/src/Field/ToggleField.php b/src/Field/ToggleField.php
new file mode 100644
index 0000000..79ff440
--- /dev/null
+++ b/src/Field/ToggleField.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class ToggleField extends CheckboxField {
+
+	public function __construct() {
+		$this->add_class( 'wpd-toggle-field' );
+	}
+
+	public function get_template_name(): string {
+		return 'input-toggle';
+	}
+}
diff --git a/templates/input-toggle.php b/templates/input-toggle.php
new file mode 100644
index 0000000..848cdf8
--- /dev/null
+++ b/templates/input-toggle.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ * @var string $template_name Real field template.
+ */
+
+?>
+
+<style>
+	input[type="checkbox"].wpd-toggle-field {
+		all:unset;
+		background: #fff;
+		border:1px solid #949494;
+		width:32px;
+		height: 16px;
+		position: relative;
+		cursor: pointer;
+		border-radius: 200px;
+	}
+	input[type="checkbox"].wpd-toggle-field::before {
+		all:unset;
+		content: "";
+		display: block !important;
+		height: 14px;
+		width:14px;
+		background: #1c1c1c;
+		border-radius: 100%;
+		position: absolute;
+		left: 1px;
+		top:1px;
+		transition: 0.25s;
+		transition-timing-function: ease-out;
+	}
+	input[type="checkbox"].wpd-toggle-field:checked {
+		background: var(--wp-admin-theme-color, #1851e0);
+		border-color: var(--wp-admin-theme-color, #1851e0);
+	}
+	input[type="checkbox"].wpd-toggle-field:checked:before {
+		left: 17px;
+		background: #fff;
+	}
+
+</style>
+<?php
+$renderer->output_render(
+	'input',
+	[
+		'field'       => $field,
+		'renderer'    => $renderer,
+		'name_prefix' => $name_prefix,
+		'value'       => $value,
+	]
+);
-- 
GitLab


From e97a84a5dc8795f0de3b85222191d9fd2d7817c2 Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Thu, 10 Apr 2025 15:16:22 +0200
Subject: [PATCH 2/9] chore: allow failure for phpstan

---
 .gitlab-ci.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b865367..1677565 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,3 +6,5 @@ variables:
   IS_LIBRARY: 1
 
 include: 'https://gitlab.com/wpdesk/gitlab-ci/raw/master/gitlab-ci-1.2.yml'
+phpstan:
+    allow_failure: true
-- 
GitLab


From 891c8b299e9580d27eacecd81d8e83afe84443bf Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Thu, 10 Apr 2025 15:28:34 +0200
Subject: [PATCH 3/9] chore: phpstan, baseline

---
 .gitlab-ci.yml        |   2 -
 .phpstan-baseline.php | 347 ++++++++++++++++++++++++++++++++++++++++++
 phpstan.neon.dist     |   2 +
 3 files changed, 349 insertions(+), 2 deletions(-)
 create mode 100644 .phpstan-baseline.php

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1677565..b865367 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,5 +6,3 @@ variables:
   IS_LIBRARY: 1
 
 include: 'https://gitlab.com/wpdesk/gitlab-ci/raw/master/gitlab-ci-1.2.yml'
-phpstan:
-    allow_failure: true
diff --git a/.phpstan-baseline.php b/.phpstan-baseline.php
new file mode 100644
index 0000000..b624e6e
--- /dev/null
+++ b/.phpstan-baseline.php
@@ -0,0 +1,347 @@
+<?php declare(strict_types = 1);
+
+$ignoreErrors = [];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:get_type\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:unset_attribute\\(\\) has invalid return type WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes\\.$#',
+	'identifier' => 'return.trait',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:unset_attribute\\(\\) should return WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes but returns \\$this\\(WPDesk\\\\Forms\\\\Field\\\\BasicField\\)\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'class\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'data\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'default_value\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'description\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'description_tip\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'disabled\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? does not exist\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'id\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'label\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'multiple\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} in isset\\(\\) does not exist\\.$#',
+	'identifier' => 'isset.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'name\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'placeholder\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'readonly\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? does not exist\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'required\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} in isset\\(\\) does not exist\\.$#',
+	'identifier' => 'isset.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'type\' does not exist on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset string on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept array\\{placeholder\\?\\: string, name\\?\\: string, id\\?\\: string, class\\?\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept default value of type array\\{\\}\\.$#',
+	'identifier' => 'property.defaultValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept non\\-empty\\-array\\<string, array\\<string\\>\\|bool\\|string\\>\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Return type \\(array\\<array\\<string\\>\\|bool\\|string\\>\\) of method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:get_attributes\\(\\) should be covariant with return type \\(array\\<string\\>\\) of method WPDesk\\\\Forms\\\\Field\\:\\:get_attributes\\(\\)$#',
+	'identifier' => 'method.childReturnType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'sublabel\' might not exist on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/CheckboxField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$action of class WPDesk\\\\Forms\\\\Validator\\\\NonceValidator constructor expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/NoOnceField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\FieldRenderer\\:\\:render_fields\\(\\) has parameter \\$fields_data with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/FieldRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\FieldRenderer\\:\\:render_fields\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/FieldRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\:\\:get_normalized_data\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\:\\:handle_request\\(\\) has parameter \\$request with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_action\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_data\\(\\) should return array\\<int\\|string\\> but returns array\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_method\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_normalized_data\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:handle_request\\(\\) has parameter \\$request with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:unset_attribute\\(\\) has invalid return type WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes\\.$#',
+	'identifier' => 'return.trait',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:unset_attribute\\(\\) should return WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes but returns \\$this\\(WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\)\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'action\' does not exist on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'method\' does not exist on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset string on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#2 \\$value of method WPDesk\\\\Persistence\\\\PersistentContainer\\:\\:set\\(\\) expects array\\|float\\|int\\|string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 2,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept array\\{placeholder\\?\\: string, name\\?\\: string, id\\?\\: string, class\\?\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept default value of type array\\{\\}\\.$#',
+	'identifier' => 'property.defaultValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept non\\-empty\\-array\\<string, array\\<string\\>\\|bool\\|string\\>\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$updated_data type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Persistence\\\\FieldPersistenceStrategy\\:\\:load_fields\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Persistence\\\\FieldPersistenceStrategy\\:\\:persist_fields\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^PHPDoc tag @return with type void is incompatible with native type array\\.$#',
+	'identifier' => 'return.phpDocType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$value of method WPDesk\\\\Forms\\\\Serializer\\:\\:unserialize\\(\\) expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#2 \\$value of method WPDesk\\\\Persistence\\\\PersistentContainer\\:\\:set\\(\\) expects array\\|float\\|int\\|string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Renderer\\\\JsonNormalizedRenderer\\:\\:render_fields\\(\\) has parameter \\$fields_data with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Renderer/JsonNormalizedRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Renderer\\\\JsonNormalizedRenderer\\:\\:render_fields\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Renderer/JsonNormalizedRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Sanitizer\\\\CallableSanitizer\\:\\:sanitize\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/CallableSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$email of function sanitize_email expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/EmailSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, \'sanitize_text_field\' given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/TextFieldSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$str of function sanitize_text_field expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/TextFieldSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Validator\\\\ChainValidator\\:\\:get_messages\\(\\) should return array\\<string\\> but returns array\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Validator/ChainValidator.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Validator\\\\ChainValidator\\:\\:\\$messages type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Validator/ChainValidator.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$nonce of function wp_verify_nonce expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Validator/NonceValidator.php',
+];
+
+return ['parameters' => ['ignoreErrors' => $ignoreErrors]];
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 200d200..422f90c 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -1,3 +1,5 @@
+includes:
+    - .phpstan-baseline.php
 parameters:
     level: max
     paths:
-- 
GitLab


From b2e743f250912044ec86ee9c107d4693102cd3c2 Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Thu, 10 Apr 2025 15:33:35 +0200
Subject: [PATCH 4/9] chore: phpstan, cbf, cs scripts in composer

---
 composer.json | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/composer.json b/composer.json
index 9274422..874f03f 100644
--- a/composer.json
+++ b/composer.json
@@ -44,6 +44,9 @@
 		}
 	},
 	"scripts": {
+		"phpcs": "phpcs",
+		"phpstan": "php -d memory_limit=1G vendor/bin/phpstan",
+		"phpcbf": "phpcbf src",
 		"phpunit-unit": "phpunit --configuration phpunit-unit.xml --coverage-text --colors=never",
 		"phpunit-unit-fast": "phpunit --configuration phpunit-unit.xml --no-coverage",
 		"phpunit-integration": "phpunit --configuration phpunit-integration.xml --coverage-text --colors=never",
-- 
GitLab


From f49de6835a235764010b9cfeee714038bf1e6766 Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Mon, 14 Apr 2025 11:01:31 +0200
Subject: [PATCH 5/9] refactor: phpcs

---
 src/Persistence/FieldPersistenceStrategy.php | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/Persistence/FieldPersistenceStrategy.php b/src/Persistence/FieldPersistenceStrategy.php
index 3102f1c..7fa7dcc 100644
--- a/src/Persistence/FieldPersistenceStrategy.php
+++ b/src/Persistence/FieldPersistenceStrategy.php
@@ -43,9 +43,7 @@ class FieldPersistenceStrategy {
 				} else {
 					$data[ $field_key ] = $this->persistence->get( $field_key );
 				}
-			} catch ( NotFoundExceptionInterface $not_found ) {
-				// TODO: Logger
-				// LoggerFactory::get_logger()->info( "FieldPersistenceStrategy:: Field {$field_key} not found" );
+			} catch ( NotFoundExceptionInterface $not_found ) { //phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
 			}
 		}
 
-- 
GitLab


From 2849c241bb03b837bcd30255632faf526950e514 Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Mon, 14 Apr 2025 11:12:07 +0200
Subject: [PATCH 6/9] chore: changelog update

---
 changelog.txt | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/changelog.txt b/changelog.txt
index fd47444..a5b85e1 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,8 +1,13 @@
 # Changelog
 
-## [3.3.0] - 2025-04-XX
+## [4.0.0] - 2025-04-XX
 ### Added
 - ToggleField class, extends CheckboxField for toggle button functionality.
+### Changed
+- Declared nullable parameter types (e.g. `?string`, `?Renderer`) to fix PHP 8.4 deprecation warnings about implicitly nullable parameters.
+### Removed
+- Support for PHP < 7.1.
+
 
 ## [3.2.1] - 2024-10-30
 ### Changed
-- 
GitLab


From 9b04361d83bc2cc848bcd65bf7d48f16388b9fbb Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Mon, 14 Apr 2025 11:12:31 +0200
Subject: [PATCH 7/9] refactor: phpstan nullable

---
 src/Field.php                             | 2 +-
 src/Field/Traits/HtmlAttributes.php       | 2 +-
 src/Resolver/DefaultFormFieldResolver.php | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/Field.php b/src/Field.php
index b932f27..14b0259 100644
--- a/src/Field.php
+++ b/src/Field.php
@@ -55,7 +55,7 @@ interface Field {
 	 */
 	public function get_attributes( array $except = [] ): array;
 
-	public function get_attribute( string $name, string $default = null ): string;
+	public function get_attribute( string $name, ?string $default = null ): string;
 
 	public function is_attribute_set( string $name ): bool;
 
diff --git a/src/Field/Traits/HtmlAttributes.php b/src/Field/Traits/HtmlAttributes.php
index aac835d..95a7a42 100644
--- a/src/Field/Traits/HtmlAttributes.php
+++ b/src/Field/Traits/HtmlAttributes.php
@@ -57,7 +57,7 @@ trait HtmlAttributes {
 		return ! empty( $this->attributes[ $name ] );
 	}
 
-	final public function get_attribute( string $name, string $default = null ): string {
+	final public function get_attribute( string $name, ?string $default = null ): string {
 		if ( is_array( $this->attributes[ $name ] ) ) {
 			// Be aware of coercing - if implode returns string(0) '', then return $default value.
 			return implode( ' ', $this->attributes[ $name ] ) ?: $default ?? '';
diff --git a/src/Resolver/DefaultFormFieldResolver.php b/src/Resolver/DefaultFormFieldResolver.php
index 4e3077a..23f5a52 100644
--- a/src/Resolver/DefaultFormFieldResolver.php
+++ b/src/Resolver/DefaultFormFieldResolver.php
@@ -20,7 +20,7 @@ class DefaultFormFieldResolver implements Resolver {
 		$this->dir_resolver = new DirResolver( __DIR__ . '/../../templates' );
 	}
 
-	public function resolve( $name, Renderer $renderer = null ): string {
+	public function resolve( $name, ?Renderer $renderer = null ): string {
 		return $this->dir_resolver->resolve( $name, $renderer );
 	}
 }
-- 
GitLab


From b019e8b2bbd7011cf129091e8a8a8ed3a83ed9af Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Mon, 14 Apr 2025 11:13:30 +0200
Subject: [PATCH 8/9] chore: updated changelog. Info for support drop is not
 necessary

---
 changelog.txt | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/changelog.txt b/changelog.txt
index a5b85e1..387ec70 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,13 +1,10 @@
 # Changelog
 
-## [4.0.0] - 2025-04-XX
+## [3.3.0] - 2025-04-XX
 ### Added
 - ToggleField class, extends CheckboxField for toggle button functionality.
 ### Changed
 - Declared nullable parameter types (e.g. `?string`, `?Renderer`) to fix PHP 8.4 deprecation warnings about implicitly nullable parameters.
-### Removed
-- Support for PHP < 7.1.
-
 
 ## [3.2.1] - 2024-10-30
 ### Changed
-- 
GitLab


From 71e0f62730ccc1bcc4116e999e6359d8675d04ba Mon Sep 17 00:00:00 2001
From: Eryk Mika <eryk.mika@wpdesk.eu>
Date: Tue, 15 Apr 2025 12:24:44 +0200
Subject: [PATCH 9/9] chore: release date update

---
 changelog.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/changelog.txt b/changelog.txt
index 387ec70..bf68f37 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,6 +1,6 @@
 # Changelog
 
-## [3.3.0] - 2025-04-XX
+## [3.3.0] - 2025-04-15
 ### Added
 - ToggleField class, extends CheckboxField for toggle button functionality.
 ### Changed
-- 
GitLab