create-prisma-php-app 1.15.6 → 1.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Desktop (please complete the following information):**
27
+ - OS: [e.g. iOS]
28
+ - Browser [e.g. chrome, safari]
29
+ - Version [e.g. 22]
30
+
31
+ **Smartphone (please complete the following information):**
32
+ - Device: [e.g. iPhone6]
33
+ - OS: [e.g. iOS8.1]
34
+ - Browser [e.g. stock browser, safari]
35
+ - Version [e.g. 22]
36
+
37
+ **Additional context**
38
+ Add any other context about the problem here.
@@ -8,51 +8,59 @@ namespace Lib;
8
8
  class StateManager
9
9
  {
10
10
  private const APP_STATE = 'app_state_F989A';
11
- private $state;
12
- private $listeners;
11
+ private array $state = [];
12
+ private array $listeners = [];
13
13
 
14
14
  /**
15
15
  * Initializes a new instance of the StateManager class.
16
- *
17
- * @param array $initialState The initial state of the application.
18
16
  */
19
- public function __construct(array $initialState = [])
17
+ public function __construct()
20
18
  {
21
19
  global $isWire;
22
20
 
23
- $this->state = $initialState;
24
- $this->listeners = [];
25
21
  $this->loadState();
26
22
 
27
- if (!$isWire) $this->resetState();
23
+ if (!$isWire) {
24
+ $this->resetState();
25
+ }
28
26
  }
29
27
 
30
28
  /**
31
- * Retrieves the current state of the application.
29
+ * Gets the state value for the specified key.
32
30
  *
33
- * @param string|null $key The key of the state value to retrieve. If null, returns the entire state.
34
- * @return mixed|null The state value corresponding to the given key, or null if the key is not found.
31
+ * @param string|null $key The key of the state value to get.
32
+ * @param mixed $initialValue The initial value to set if the key does not exist.
33
+ * @return mixed The state value for the specified key.
35
34
  */
36
- public function getState($key = null): mixed
35
+ public function getState(string $key = null, mixed $initialValue = null): mixed
37
36
  {
38
37
  if ($key === null) {
39
38
  return new \ArrayObject($this->state, \ArrayObject::ARRAY_AS_PROPS);
40
39
  }
41
40
 
42
- $value = $this->state[$key] ?? null;
41
+ if (!array_key_exists($key, $this->state)) {
42
+ if ($initialValue !== null) {
43
+ $this->setState($key, $initialValue);
44
+ }
45
+ } elseif ($initialValue !== null && $this->state[$key] !== $initialValue) {
46
+ $this->setState($key, $this->state[$key]);
47
+ }
48
+
49
+ $value = $this->state[$key] ?? $initialValue;
50
+
43
51
  return is_array($value) ? new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS) : $value;
44
52
  }
45
53
 
46
54
  /**
47
- * Updates the application state with the given update.
55
+ * Sets the state value for the specified key.
48
56
  *
49
- * @param string|array $key The key of the state value to update, or an array of key-value pairs to update multiple values.
50
- * @param mixed|null $value The value to update the state with, ignored if $key is an array.
57
+ * @param string $key The key of the state value to set.
58
+ * @param mixed $value The value to set.
51
59
  */
52
- public function setState($key, $value = null): void
60
+ public function setState(string $key, mixed $value = null): void
53
61
  {
54
- $update = is_array($key) ? $key : [$key => $value];
55
- $this->state = array_merge($this->state, $update);
62
+ $this->state[$key] = $value;
63
+
56
64
  $this->notifyListeners();
57
65
  $this->saveState();
58
66
  }
@@ -66,10 +74,8 @@ class StateManager
66
74
  public function subscribe(callable $listener): callable
67
75
  {
68
76
  $this->listeners[] = $listener;
69
- $listener($this->state); // Immediate call with current state
70
- return function () use ($listener) {
71
- $this->listeners = array_filter($this->listeners, fn ($l) => $l !== $listener);
72
- };
77
+ $listener($this->state);
78
+ return fn () => $this->listeners = array_filter($this->listeners, fn ($l) => $l !== $listener);
73
79
  }
74
80
 
75
81
  /**
@@ -77,7 +83,7 @@ class StateManager
77
83
  */
78
84
  private function saveState(): void
79
85
  {
80
- $_SESSION[self::APP_STATE] = json_encode($this->state);
86
+ $_SESSION[self::APP_STATE] = json_encode($this->state, JSON_THROW_ON_ERROR);
81
87
  }
82
88
 
83
89
  /**
@@ -86,8 +92,8 @@ class StateManager
86
92
  private function loadState(): void
87
93
  {
88
94
  if (isset($_SESSION[self::APP_STATE])) {
89
- $loadedState = json_decode($_SESSION[self::APP_STATE], true);
90
- if ($loadedState !== null) {
95
+ $loadedState = json_decode($_SESSION[self::APP_STATE], true, 512, JSON_THROW_ON_ERROR);
96
+ if (is_array($loadedState)) {
91
97
  $this->state = $loadedState;
92
98
  $this->notifyListeners();
93
99
  }
@@ -1 +1 @@
1
- "use strict";var eventAttributes=["onclick","ondblclick","onmousedown","onmouseup","onmouseover","onmousemove","onmouseout","onwheel","onkeypress","onkeydown","onkeyup","onfocus","onblur","onchange","oninput","onselect","onsubmit","onreset","onresize","onscroll","onload","onunload","onabort","onerror","onbeforeunload","oncopy","oncut","onpaste","ondrag","ondragstart","ondragend","ondragover","ondragenter","ondragleave","ondrop","oncontextmenu","ontouchstart","ontouchmove","ontouchend","ontouchcancel","onpointerdown","onpointerup","onpointermove","onpointerover","onpointerout","onpointerenter","onpointerleave","onpointercancel"];document.addEventListener("DOMContentLoaded",attachWireFunctionEvents);const optimisticUpdates=new Map,state={checkedElements:new Set};function attachWireFunctionEvents(){document.querySelectorAll("button, input, select, textarea, a, form, label, div, span").forEach((t=>{if(t instanceof HTMLAnchorElement&&t.addEventListener("click",handleAnchorTag),eventAttributes.forEach((e=>{const n=t.getAttribute(e),o=e.slice(2);n&&(t.removeAttribute(e),handleDebounce(t,o,n))})),t instanceof HTMLFormElement){const e=t.getAttribute("onsubmit");e&&(t.removeAttribute("onsubmit"),handleDebounce(t,"submit",e))}}))}async function handleDebounce(t,e,n){const o=t.getAttribute("pp-debounce"),a=t.getAttribute("pp-trigger"),s=async e=>{e.preventDefault();const o=saveOptimisticState(t);optimisticUpdates.set(t,o);try{a&&await invokeHandler(t,a),await invokeHandler(t,n)}catch(e){const n=optimisticUpdates.get(t);n&&revertOptimisticUpdate(t,n)}};if(o){const n=debounce(s,parseInt(o,10));t instanceof HTMLFormElement&&"submit"===e?t.addEventListener(e,(t=>{t.preventDefault(),n(t)})):t.addEventListener(e,n)}else t.addEventListener(e,s)}async function invokeHandler(t,e){const{funcName:n,data:o}=parseCallback(t,e);if(n){const t=window[n];if("function"==typeof t)try{const e=Array.isArray(o.args)?o.args:[];await t(...e,o)}catch(t){}else await handleUndefinedFunction(n,o)}}function extractStyles(t){const e={};for(let n=0;n<t.length;n++){const o=t[n];e[o]=t.getPropertyValue(o)}return e}function applyOptimisticUpdate(t,e){Object.entries(e.attributes).forEach((([e,n])=>{n?t.setAttribute(e,n):t.removeAttribute(e)})),Object.assign(t.style,e.styles),t.innerHTML=e.innerHTML||""}function revertOptimisticUpdate(t,e){"value"in e&&("checkbox"===t.type||"radio"===t.type?t.checked=!!e.value:t.value=e.value),"innerHTML"in e&&(t.innerHTML=e.innerHTML)}async function handleAnchorTag(t){const e=t.currentTarget,n=e.getAttribute("href"),o=e.getAttribute("target");if(!n||"_blank"===o||t.metaKey||t.ctrlKey)return;t.preventDefault();if(/^(https?:)?\/\//i.test(n)&&!n.startsWith(window.location.origin))window.location.href=n;else try{history.pushState(null,"",n),window.dispatchEvent(new PopStateEvent("popstate",{state:null}))}catch(t){}}function updateDocumentContent(t){if(t.includes("<!DOCTYPE html>")){const e=(new DOMParser).parseFromString(t,"text/html");document.replaceChild(document.adoptNode(e.documentElement),document.documentElement)}else{saveState();const e=saveScrollPositions(),n=(new DOMParser).parseFromString(t,"text/html"),o=Array.from(n.body.querySelectorAll("script")),a=n.body;diffAndPatch(document.body,a),restoreState(),restoreScrollPositions(e),o.forEach((t=>{if(t.src)loadScript(t.src);else{const e=document.createElement("script");e.textContent=t.textContent,document.body.appendChild(e),document.body.removeChild(e)}}))}attachWireFunctionEvents()}function diffAndPatch(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE||e.nodeType!==Node.TEXT_NODE?t instanceof HTMLElement&&e instanceof HTMLElement&&t.replaceWith(e):t.textContent!==e.textContent&&(t.textContent=e.textContent):t.parentNode?.replaceChild(e,t)}function updateAttributes(t,e){Array.from(e.attributes).forEach((e=>{t.getAttribute(e.name)!==e.value&&t.setAttribute(e.name,e.value)})),Array.from(t.attributes).forEach((n=>{e.hasAttribute(n.name)||t.removeAttribute(n.name)}))}function updateChildren(t,e){const n=t.childNodes,o=e.childNodes,a=Math.max(n.length,o.length);for(let e=0;e<a;e++){const a=n[e],s=o[e];a?s?diffAndPatch(a,s):t.removeChild(a):t.appendChild(s)}}function loadScript(t){const e=document.createElement("script");e.src=t,document.head.appendChild(e)}function saveState(){const t=document.activeElement;state.focusId=t?.id||t?.name,state.focusValue=t?.value,state.focusSelectionStart=t?.selectionStart,state.focusSelectionEnd=t?.selectionEnd,state.checkedElements.clear(),document.querySelectorAll('input[type="checkbox"]:checked').forEach((t=>{state.checkedElements.add(t.id||t.name)}))}function restoreState(){if(state.focusId){const t=document.getElementById(state.focusId)||document.querySelector(`[name="${state.focusId}"]`);t instanceof HTMLInputElement&&(t.focus(),"search"===t.type&&(t.value=state.focusValue||""),void 0!==state.focusSelectionStart&&null!==state.focusSelectionEnd&&t.setSelectionRange(state.focusSelectionStart||null,state.focusSelectionEnd||null))}state.checkedElements.forEach((t=>{const e=document.getElementById(t);e&&(e.checked=!0)}))}function saveScrollPositions(){const t={};return document.querySelectorAll("*").forEach((e=>{(e.scrollTop||e.scrollLeft)&&(t[getElementKey(e)]={scrollTop:e.scrollTop,scrollLeft:e.scrollLeft})})),t}function restoreScrollPositions(t){document.querySelectorAll("*").forEach((e=>{const n=getElementKey(e);t[n]&&(e.scrollTop=t[n].scrollTop,e.scrollLeft=t[n].scrollLeft)}))}function getElementKey(t){return t.id||t.className||t.tagName}function parseCallback(t,e){let n={};const o=t.closest("form");if(o){new FormData(o).forEach(((t,e)=>{n[e]?Array.isArray(n[e])?n[e].push(t):n[e]=[n[e],t]:n[e]=t}))}else t instanceof HTMLInputElement?n=handleInputElement(t):(t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement)&&(n[t.name]=t.value);const a=e.match(/(\w+)\((.*)\)/);if(a){const t=a[1];let e=a[2].trim();if(e.startsWith("{")&&e.endsWith("}")){const t=e.replace(/'/g,'"');try{const e=JSON.parse(t);"object"==typeof e&&null!==e&&(n={...n,...e})}catch(t){}}else{const t=e.split(/,(?=(?:[^'"]*['"][^'"]*['"])*[^'"]*$)/).map((t=>t.trim().replace(/^['"]|['"]$/g,"")));n.args=t}return{funcName:t,data:n}}return{funcName:e,data:n}}function handleInputElement(t){let e={};return t.name?"checkbox"===t.type||"radio"===t.type?e[t.name]=t.checked:e[t.name]=t.value:"checkbox"===t.type||"radio"===t.type?e.value=t.checked:e.value=t.value,e}function captureState(t){const e={};for(const n of t.attributes)e[n.name]=n.value;return{attributes:e,styles:extractStyles(window.getComputedStyle(t)),innerHTML:t.innerHTML||""}}async function handleUndefinedFunction(t,e){const n={callback:t,...e},o={method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",HTTP_PPHP_WIRE_REQUEST:"true"},body:JSON.stringify(n)},a={method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",HTTP_PPHP_WIRE_REQUEST:"true"},body:JSON.stringify({secondRequest:!0})},s=async t=>{const e=await fetch(window.location.pathname,t);return await e.text()};try{const t=await s(o),e=await s(a);if(t.includes("redirect_7F834=")){const e=t.split("=")[1];await handleRedirect(e)}else{updateDocumentContent(t+e)}}catch(t){revertOptimisticUpdate(document.body,captureState(document.body))}}function saveOptimisticState(t){return{value:t.value||t.checked||"",attributes:Array.from(t.attributes).reduce(((t,e)=>(t[e.name]=e.value,t)),{}),styles:extractStyles(window.getComputedStyle(t)),innerHTML:t.innerHTML}}async function handleRedirect(t){if(t){history.pushState(null,"",t),window.dispatchEvent(new PopStateEvent("popstate",{state:null}));try{const e=await fetch(t,{headers:{"X-Requested-With":"XMLHttpRequest"}});updateDocumentContent(await e.text())}catch(t){}}}function debounce(t,e=300,n=!1){let o;return function(...a){const s=this;o&&clearTimeout(o),o=setTimeout((()=>{o=null,n||t.apply(s,a)}),e),n&&!o&&t.apply(s,a)}}function copyCode(t,e,n,o,a=2e3){const s=t.closest(`.${e}`)?.querySelector("pre code"),c=s?.textContent?.trim()||"";c?navigator.clipboard.writeText(c).then((()=>{const e=t.querySelector("i");e&&(e.className=o),setTimeout((()=>{e&&(e.className=n)}),a)}),(()=>{alert("Failed to copy command to clipboard")})):alert("Failed to find the code block to copy")}window.addEventListener("popstate",(async()=>{try{const t=await fetch(window.location.href,{headers:{"X-Requested-With":"XMLHttpRequest"}});updateDocumentContent(await t.text())}catch(t){}}));let api=null;if(void 0===api){class t{static instance=null;baseURL;constructor(t=window.location.origin){this.baseURL=t}static getInstance(e=window.location.origin){return t.instance||(t.instance=new t(e)),t.instance}async request(t,e,n=null,o={}){let a=`${this.baseURL}${e}`;const s={method:t,headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",...o}};if(n)if("GET"===t){a+=`?${new URLSearchParams(n).toString()}`}else"HEAD"!==t&&"OPTIONS"!==t&&(s.body=JSON.stringify(n));try{const e=await fetch(a,s);if("HEAD"===t)return e.headers;const n=e.headers.get("content-type");return n&&n.includes("application/json")?await e.json():await e.text()}catch(t){throw t}}get(t,e,n){return this.request("GET",t,e,n)}post(t,e,n){return this.request("POST",t,e,n)}put(t,e,n){return this.request("PUT",t,e,n)}delete(t,e,n){return this.request("DELETE",t,e,n)}patch(t,e,n){return this.request("PATCH",t,e,n)}head(t,e){return this.request("HEAD",t,null,e)}options(t,e){return this.request("OPTIONS",t,null,e)}}api=t.getInstance()}let store=null;if(void 0===store){class t{static instance=null;state;listeners;constructor(t={}){this.state=t,this.listeners=[]}static getInstance(e={}){return t.instance||(t.instance=new t(e),t.instance.loadState()),t.instance}setState(t,e=!1){this.state={...this.state,...t},this.listeners.forEach((t=>t(this.state))),e&&this.saveState()}subscribe(t){return this.listeners.push(t),t(this.state),()=>{this.listeners=this.listeners.filter((e=>e!==t))}}saveState(){localStorage.setItem("appState",JSON.stringify(this.state))}loadState(){const t=localStorage.getItem("appState");t&&(this.state=JSON.parse(t),this.listeners.forEach((t=>t(this.state))))}resetState(t=!1){this.state={},this.listeners.forEach((t=>t(this.state))),t&&localStorage.removeItem("appState")}}store=t.getInstance()}
1
+ "use strict";var eventAttributes=["onclick","ondblclick","onmousedown","onmouseup","onmouseover","onmousemove","onmouseout","onwheel","onkeypress","onkeydown","onkeyup","onfocus","onblur","onchange","oninput","onselect","onsubmit","onreset","onresize","onscroll","onload","onunload","onabort","onerror","onbeforeunload","oncopy","oncut","onpaste","ondrag","ondragstart","ondragend","ondragover","ondragenter","ondragleave","ondrop","oncontextmenu","ontouchstart","ontouchmove","ontouchend","ontouchcancel","onpointerdown","onpointerup","onpointermove","onpointerover","onpointerout","onpointerenter","onpointerleave","onpointercancel"];document.addEventListener("DOMContentLoaded",attachWireFunctionEvents);const optimisticUpdates=new Map,state={checkedElements:new Set};function attachWireFunctionEvents(){document.querySelectorAll("button, input, select, textarea, a, form, label, div, span").forEach((t=>{if(t instanceof HTMLAnchorElement&&t.addEventListener("click",handleAnchorTag),eventAttributes.forEach((e=>{const n=t.getAttribute(e),o=e.slice(2);n&&(t.removeAttribute(e),handleDebounce(t,o,n))})),t instanceof HTMLFormElement){const e=t.getAttribute("onsubmit");e&&(t.removeAttribute("onsubmit"),handleDebounce(t,"submit",e))}}))}async function handleDebounce(t,e,n){const o=t.getAttribute("pp-debounce"),a=t.getAttribute("pp-trigger"),s=async e=>{e.preventDefault();const o=saveOptimisticState(t);optimisticUpdates.set(t,o);try{a&&await invokeHandler(t,a),await invokeHandler(t,n)}catch(e){const n=optimisticUpdates.get(t);n&&revertOptimisticUpdate(t,n)}};if(o){const n=debounce(s,parseInt(o,10));t instanceof HTMLFormElement&&"submit"===e?t.addEventListener(e,(t=>{t.preventDefault(),n(t)})):t.addEventListener(e,n)}else t.addEventListener(e,s)}async function invokeHandler(t,e){try{const n=e.match(/^(\w+(\.\w+)*)\((.*)\)$/);if(n){const o=n[1],a=n[3],s=o.split("."),{context:c,methodName:r}=resolveContext(s);if("function"==typeof c[r]){const t=parseArguments(a);await c[r](...t)}else await handleParsedCallback(t,e)}else await handleParsedCallback(t,e)}catch(t){}}function resolveContext(t){let e=window;for(let n=0;n<t.length-1;n++)if(e=e[t[n]],!e)throw new Error(`Cannot find object ${t[n]} in the context.`);return{context:e,methodName:t[t.length-1]}}function parseArguments(t){return t?JSON.parse(`[${t}]`):[]}async function handleParsedCallback(t,e){const{funcName:n,data:o}=parseCallback(t,e);if(!n)return;const a=window[n];if("function"==typeof a){const t=Array.isArray(o.args)?o.args:[];await a(...t,o)}else await handleUndefinedFunction(n,o)}function extractStyles(t){const e={};for(let n=0;n<t.length;n++){const o=t[n];e[o]=t.getPropertyValue(o)}return e}function applyOptimisticUpdate(t,e){Object.entries(e.attributes).forEach((([e,n])=>{n?t.setAttribute(e,n):t.removeAttribute(e)})),Object.assign(t.style,e.styles),t.innerHTML=e.innerHTML||""}function revertOptimisticUpdate(t,e){"value"in e&&("checkbox"===t.type||"radio"===t.type?t.checked=!!e.value:t.value=e.value),"innerHTML"in e&&(t.innerHTML=e.innerHTML)}async function handleAnchorTag(t){const e=t.currentTarget,n=e.getAttribute("href"),o=e.getAttribute("target");if(!n||"_blank"===o||t.metaKey||t.ctrlKey)return;t.preventDefault();if(/^(https?:)?\/\//i.test(n)&&!n.startsWith(window.location.origin))window.location.href=n;else try{history.pushState(null,"",n),window.dispatchEvent(new PopStateEvent("popstate",{state:null}))}catch(t){}}function updateDocumentContent(t){if(t.includes("<!DOCTYPE html>")){const e=(new DOMParser).parseFromString(t,"text/html");document.replaceChild(document.adoptNode(e.documentElement),document.documentElement)}else{saveState();const e=saveScrollPositions(),n=(new DOMParser).parseFromString(t,"text/html"),o=Array.from(n.body.querySelectorAll("script")),a=n.body;diffAndPatch(document.body,a),restoreState(),restoreScrollPositions(e),o.forEach((t=>{if(t.src)loadScript(t.src);else{const e=document.createElement("script");e.textContent=t.textContent,document.body.appendChild(e),document.body.removeChild(e)}}))}attachWireFunctionEvents()}function diffAndPatch(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE||e.nodeType!==Node.TEXT_NODE?t instanceof HTMLElement&&e instanceof HTMLElement&&t.replaceWith(e):t.textContent!==e.textContent&&(t.textContent=e.textContent):t.parentNode?.replaceChild(e,t)}function updateAttributes(t,e){Array.from(e.attributes).forEach((e=>{t.getAttribute(e.name)!==e.value&&t.setAttribute(e.name,e.value)})),Array.from(t.attributes).forEach((n=>{e.hasAttribute(n.name)||t.removeAttribute(n.name)}))}function updateChildren(t,e){const n=t.childNodes,o=e.childNodes,a=Math.max(n.length,o.length);for(let e=0;e<a;e++){const a=n[e],s=o[e];a?s?diffAndPatch(a,s):t.removeChild(a):t.appendChild(s)}}function loadScript(t){const e=document.createElement("script");e.src=t,document.head.appendChild(e)}function saveState(){const t=document.activeElement;state.focusId=t?.id||t?.name,state.focusValue=t?.value,state.focusSelectionStart=t?.selectionStart,state.focusSelectionEnd=t?.selectionEnd,state.checkedElements.clear(),document.querySelectorAll('input[type="checkbox"]:checked').forEach((t=>{state.checkedElements.add(t.id||t.name)}))}function restoreState(){if(state.focusId){const t=document.getElementById(state.focusId)||document.querySelector(`[name="${state.focusId}"]`);t instanceof HTMLInputElement&&(t.focus(),"search"===t.type&&(t.value=state.focusValue||""),void 0!==state.focusSelectionStart&&null!==state.focusSelectionEnd&&t.setSelectionRange(state.focusSelectionStart||null,state.focusSelectionEnd||null))}state.checkedElements.forEach((t=>{const e=document.getElementById(t);e&&(e.checked=!0)}))}function saveScrollPositions(){const t={};return document.querySelectorAll("*").forEach((e=>{(e.scrollTop||e.scrollLeft)&&(t[getElementKey(e)]={scrollTop:e.scrollTop,scrollLeft:e.scrollLeft})})),t}function restoreScrollPositions(t){document.querySelectorAll("*").forEach((e=>{const n=getElementKey(e);t[n]&&(e.scrollTop=t[n].scrollTop,e.scrollLeft=t[n].scrollLeft)}))}function getElementKey(t){return t.id||t.className||t.tagName}function parseCallback(t,e){let n={};const o=t.closest("form");if(o){new FormData(o).forEach(((t,e)=>{n[e]?Array.isArray(n[e])?n[e].push(t):n[e]=[n[e],t]:n[e]=t}))}else t instanceof HTMLInputElement?n=handleInputElement(t):(t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement)&&(n[t.name]=t.value);const a=e.match(/(\w+)\((.*)\)/);if(a){const t=a[1];let e=a[2].trim();if(e.startsWith("{")&&e.endsWith("}")){const t=e.replace(/'/g,'"');try{const e=JSON.parse(t);"object"==typeof e&&null!==e&&(n={...n,...e})}catch(t){}}else{const t=e.split(/,(?=(?:[^'"]*['"][^'"]*['"])*[^'"]*$)/).map((t=>t.trim().replace(/^['"]|['"]$/g,"")));n.args=t}return{funcName:t,data:n}}return{funcName:e,data:n}}function handleInputElement(t){let e={};return t.name?"checkbox"===t.type||"radio"===t.type?e[t.name]=t.checked:e[t.name]=t.value:"checkbox"===t.type||"radio"===t.type?e.value=t.checked:e.value=t.value,e}function captureState(t){const e={};for(const n of t.attributes)e[n.name]=n.value;return{attributes:e,styles:extractStyles(window.getComputedStyle(t)),innerHTML:t.innerHTML||""}}async function handleUndefinedFunction(t,e){const n={callback:t,...e},o={method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",HTTP_PPHP_WIRE_REQUEST:"true"},body:JSON.stringify(n)},a={method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",HTTP_PPHP_WIRE_REQUEST:"true"},body:JSON.stringify({secondRequest:!0})},s=async t=>{const e=await fetch(window.location.pathname,t);return await e.text()};try{const t=await s(o),e=await s(a);if(t.includes("redirect_7F834=")){const e=t.split("=")[1];await handleRedirect(e)}else{updateDocumentContent(t+e)}}catch(t){revertOptimisticUpdate(document.body,captureState(document.body))}}function saveOptimisticState(t){return{value:t.value||t.checked||"",attributes:Array.from(t.attributes).reduce(((t,e)=>(t[e.name]=e.value,t)),{}),styles:extractStyles(window.getComputedStyle(t)),innerHTML:t.innerHTML}}async function handleRedirect(t){if(t){history.pushState(null,"",t),window.dispatchEvent(new PopStateEvent("popstate",{state:null}));try{const e=await fetch(t,{headers:{"X-Requested-With":"XMLHttpRequest"}});updateDocumentContent(await e.text())}catch(t){}}}function debounce(t,e=300,n=!1){let o;return function(...a){const s=this;o&&clearTimeout(o),o=setTimeout((()=>{o=null,n||t.apply(s,a)}),e),n&&!o&&t.apply(s,a)}}function copyCode(t,e,n,o,a=2e3){const s=t.closest(`.${e}`)?.querySelector("pre code"),c=s?.textContent?.trim()||"";c?navigator.clipboard.writeText(c).then((()=>{const e=t.querySelector("i");e&&(e.className=o),setTimeout((()=>{e&&(e.className=n)}),a)}),(()=>{alert("Failed to copy command to clipboard")})):alert("Failed to find the code block to copy")}window.addEventListener("popstate",(async()=>{try{const t=await fetch(window.location.href,{headers:{"X-Requested-With":"XMLHttpRequest"}});updateDocumentContent(await t.text())}catch(t){}}));let api=null;if(void 0===api){class t{static instance=null;baseURL;constructor(t=window.location.origin){this.baseURL=t}static getInstance(e=window.location.origin){return t.instance||(t.instance=new t(e)),t.instance}async request(t,e,n=null,o={}){let a=`${this.baseURL}${e}`;const s={method:t,headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",...o}};if(n)if("GET"===t){a+=`?${new URLSearchParams(n).toString()}`}else"HEAD"!==t&&"OPTIONS"!==t&&(s.body=JSON.stringify(n));try{const e=await fetch(a,s);if("HEAD"===t)return e.headers;const n=e.headers.get("content-type");return n&&n.includes("application/json")?await e.json():await e.text()}catch(t){throw t}}get(t,e,n){return this.request("GET",t,e,n)}post(t,e,n){return this.request("POST",t,e,n)}put(t,e,n){return this.request("PUT",t,e,n)}delete(t,e,n){return this.request("DELETE",t,e,n)}patch(t,e,n){return this.request("PATCH",t,e,n)}head(t,e){return this.request("HEAD",t,null,e)}options(t,e){return this.request("OPTIONS",t,null,e)}}api=t.getInstance()}let store=null;if(void 0===store){class t{static instance=null;state;listeners;constructor(t={}){this.state=t,this.listeners=[]}static getInstance(e={}){return t.instance||(t.instance=new t(e),t.instance.loadState()),t.instance}setState(t,e=!1){this.state={...this.state,...t},this.listeners.forEach((t=>t(this.state))),e&&this.saveState()}subscribe(t){return this.listeners.push(t),t(this.state),()=>{this.listeners=this.listeners.filter((e=>e!==t))}}saveState(){localStorage.setItem("appState",JSON.stringify(this.state))}loadState(){const t=localStorage.getItem("appState");t&&(this.state=JSON.parse(t),this.listeners.forEach((t=>t(this.state))))}resetState(t=!1){this.state={},this.listeners.forEach((t=>t(this.state))),t&&localStorage.removeItem("appState")}}store=t.getInstance()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "1.15.6",
3
+ "version": "1.15.8",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,604 +0,0 @@
1
- <?php
2
-
3
- namespace Lib;
4
-
5
- use Lib\StateManager;
6
- use Lib\Validator;
7
-
8
- class FormHandler
9
- {
10
- private $data;
11
- private $errors;
12
- private $validated;
13
- private $isPost;
14
- private $pathname;
15
- private StateManager $stateManager;
16
- private const FORM_STATE = 'pphp_form_state_977A9';
17
- private const FORM_INPUT_REGISTER = 'pphp_form_input_register_7A16F';
18
- private const FORM_INPUT_ERRORS = 'pphp_form_input_errors_CBF6C';
19
-
20
- public function __construct($formData = [])
21
- {
22
- global $isPost, $pathname;
23
-
24
- $this->isPost = $isPost;
25
- $this->pathname = $pathname;
26
- $this->data = $formData;
27
- $this->errors = [];
28
- $this->validated = false;
29
-
30
- $this->stateManager = new StateManager();
31
-
32
- if ($this->stateManager->getState(self::FORM_INPUT_REGISTER)) {
33
- $this->getData();
34
- }
35
- }
36
-
37
- /**
38
- * Validates the form data.
39
- *
40
- * @return bool True if the form data is valid, false otherwise.
41
- */
42
- public function validate(): bool
43
- {
44
- return empty($this->errors) && $this->validated;
45
- }
46
-
47
- public function addError($field, $message)
48
- {
49
- $this->errors[$field] = $message;
50
- }
51
-
52
- /**
53
- * Retrieves the form data and performs validation if the form was submitted.
54
- *
55
- * @return mixed An object containing the form data.
56
- */
57
- public function getData(): mixed
58
- {
59
- if ($this->isPost) {
60
- if ($inputField = $this->stateManager->getState(self::FORM_INPUT_REGISTER)) {
61
- foreach ($inputField as $field => $fieldData) {
62
- $this->data[$field] = Validator::string($this->data[$field] ?? '');
63
- $this->validateField($field, $fieldData['rules']);
64
- }
65
- }
66
-
67
- $formDataInfo = [
68
- 'data' => $this->data,
69
- 'errors' => $this->errors,
70
- 'validated' => true
71
- ];
72
-
73
- $this->stateManager->resetState(self::FORM_INPUT_ERRORS, true);
74
- $this->stateManager->setState([self::FORM_INPUT_ERRORS => $formDataInfo], true);
75
- $this->stateManager->setState([self::FORM_STATE => $formDataInfo], true);
76
-
77
- redirect($this->pathname);
78
- } else {
79
- if ($state = $this->stateManager->getState(self::FORM_STATE)) {
80
- $this->data = $state['data'] ?? [];
81
- $this->errors = $state['errors'] ?? [];
82
- $this->validated = $state['validated'] ?? false;
83
-
84
- $this->stateManager->resetState([self::FORM_STATE, self::FORM_INPUT_REGISTER], true);
85
- }
86
- }
87
-
88
- return new \ArrayObject($this->data, \ArrayObject::ARRAY_AS_PROPS);
89
- }
90
-
91
- /**
92
- * Retrieves the validation errors from the form state.
93
- *
94
- * This function provides error messages for individual fields or returns all form errors if no specific field is requested.
95
- * Error messages are wrapped in HTML `<span>` tags with unique IDs to facilitate identification and styling.
96
- *
97
- * - If a field name is provided:
98
- * - Returns the error message for the specific field as a span element with a unique `id` attribute.
99
- * - If no error message is available for the field, returns an empty span element.
100
- * - If no field name is provided:
101
- * - Returns an associative array of all error messages, each wrapped in a span element with a unique `id` attribute.
102
- * - If no errors are found, returns an empty array.
103
- * - If the form has not been validated yet:
104
- * - Returns an empty string if a specific field name is provided.
105
- * - Returns an empty array if no field name is provided.
106
- *
107
- * @param string|null $field The name of the field to retrieve errors for. If null, returns all errors.
108
- * - Must be a valid string or `null`.
109
- * - If provided, the resulting span element will have a unique `id` attribute prefixed with "fh-error-".
110
- *
111
- * @param string $class (optional) Additional classes to assign to the error `<span>` element.
112
- * - Defaults to an empty string.
113
- *
114
- * @return mixed If a field name is provided, returns the error message wrapped in a span or an empty string if no error.
115
- * If no field name is provided, returns an associative array of all errors or an empty array if no errors.
116
- * If the form has not been validated yet, returns an empty string.
117
- *
118
- * @example
119
- * Example usage to get a specific field's error message with a custom class:
120
- * echo $form->getErrors('email', 'form-error');
121
- * This will generate: "<span class='form-error' id='fh-error-email'>Error message here</span>"
122
- *
123
- * Example usage to get all error messages:
124
- * print_r($form->getErrors());
125
- * This will generate an associative array like:
126
- * [
127
- * 'email' => "<span class='form-error' id='fh-error-email'>Invalid email</span>",
128
- * 'username' => "<span class='form-error' id='fh-error-username'>Username too short</span>"
129
- * ]
130
- */
131
- public function getErrors(string $field = null): mixed
132
- {
133
- $wrapError = function (string $field, string $message) {
134
- return "id='fh-error-$field' data-error-message='$message'";
135
- };
136
-
137
- $field = Validator::string($field);
138
- $state = $this->stateManager->getState(self::FORM_INPUT_ERRORS);
139
-
140
- if ($this->validated && $state) {
141
- if ($field) {
142
- $errorState = $state['errors'] ?? [];
143
- return $wrapError($field, $errorState[$field] ?? '');
144
- }
145
-
146
- $errors = $state['errors'] ?? [];
147
- foreach ($errors as $fieldName => $message) {
148
- $errors[$fieldName] = $wrapError($fieldName, $message);
149
- }
150
-
151
- return $errors;
152
- }
153
-
154
- if ($field) {
155
- $fieldData = $this->data[$field] ?? '';
156
- return $wrapError($field, $fieldData);
157
- }
158
-
159
- return [];
160
- }
161
-
162
- public function clearErrors()
163
- {
164
- $this->stateManager->resetState(self::FORM_INPUT_ERRORS, true);
165
- }
166
-
167
- /**
168
- * Validates a form field based on the provided rules.
169
- *
170
- * @param string $field The name of the field to validate.
171
- * @param array $rules An associative array of rules to apply. Each key is the rule name, and the value is the rule options.
172
- * The options can be a scalar value or an array with 'value' and 'message' keys.
173
- * The 'value' key is the value to compare with, and the 'message' key is the custom error message.
174
- *
175
- * Supported rules:
176
- * - text, search, email, password, number, date, color, range, tel, url, time, datetime-local, month, week, file
177
- * - required, min, max, minLength, maxLength, pattern, autocomplete, readonly, disabled, placeholder, autofocus, multiple, accept, size, step, list
178
- *
179
- * Custom error messages can be provided for each rule. If not provided, a default message is used.
180
- *
181
- * @example
182
- * $form->validateField('email', [
183
- * 'required' => ['value' => true, 'message' => 'Email is required.'],
184
- * 'email' => ['value' => true, 'message' => 'Please enter a valid email address.']
185
- * ]);
186
- *
187
- * @return void
188
- */
189
- public function validateField($field, $rules)
190
- {
191
- $value = Validator::string($this->data[$field] ?? null);
192
-
193
- if (!isset($rules['required']) && empty($value)) {
194
- return;
195
- }
196
-
197
- foreach ($rules as $rule => $options) {
198
- $ruleValue = $options;
199
- $customMessage = null;
200
-
201
- if (is_array($options)) {
202
- $ruleValue = $options['value'];
203
- $customMessage = $options['message'] ?? null;
204
- }
205
-
206
- switch ($rule) {
207
- case 'text':
208
- case 'search':
209
- if (!is_string($value)) $this->addError($field, $customMessage ?? 'Must be a string.');
210
- break;
211
- case 'email':
212
- if (!filter_var($value, FILTER_VALIDATE_EMAIL)) $this->addError($field, $customMessage ?? 'Invalid email format.');
213
- break;
214
- case 'number':
215
- if (!is_numeric($value)) $this->addError($field, $customMessage ?? 'Must be a number.');
216
- break;
217
- case 'date':
218
- if (!\DateTime::createFromFormat('Y-m-d', $value)) $this->addError($field, $customMessage ?? 'Invalid date format.');
219
- break;
220
- case 'range':
221
- if (!is_numeric($value) || $value < $ruleValue[0] || $value > $ruleValue[1]) $this->addError($field, $customMessage ?? "Must be between $ruleValue[0] and $ruleValue[1].");
222
- break;
223
- case 'url':
224
- if (!filter_var($value, FILTER_VALIDATE_URL)) $this->addError($field, $customMessage ?? 'Invalid URL format.');
225
- break;
226
- case 'datetime-local':
227
- if (!\DateTime::createFromFormat('Y-m-d\TH:i', $value)) $this->addError($field, $customMessage ?? 'Invalid datetime-local format.');
228
- break;
229
- case 'file':
230
- if (!is_uploaded_file($value)) $this->addError($field, $customMessage ?? 'Invalid file format.');
231
- break;
232
- case 'required':
233
- if (empty($value)) $this->addError($field, $customMessage ?? 'This field is required.');
234
- break;
235
- case 'min':
236
- if ($value < $ruleValue) $this->addError($field, $customMessage ?? "Must be at least $ruleValue.");
237
- break;
238
- case 'max':
239
- if ($value > $ruleValue) $this->addError($field, $customMessage ?? "Must be at most $ruleValue.");
240
- break;
241
- case 'minLength':
242
- if (strlen($value) < $ruleValue) $this->addError($field, $customMessage ?? "Must be at least $ruleValue characters.");
243
- break;
244
- case 'maxLength':
245
- if (strlen($value) > $ruleValue) $this->addError($field, $customMessage ?? "Must be at most $ruleValue characters.");
246
- break;
247
- case 'pattern':
248
- if (!preg_match("/$ruleValue/", $value)) $this->addError($field, $customMessage ?? 'Invalid format.');
249
- break;
250
- case 'accept':
251
- if (!in_array($value, explode(',', $ruleValue))) $this->addError($field, $customMessage ?? 'Invalid file format.');
252
- break;
253
- case 'autocomplete':
254
- if (!in_array($value, ['on', 'off'])) $this->addError($field, $customMessage ?? 'Invalid autocomplete value.');
255
- break;
256
- default:
257
- // Optionally handle unknown rules or log them
258
- break;
259
- }
260
- }
261
- }
262
-
263
- /**
264
- * Registers a form field and its validation rules, and updates the form state.
265
- *
266
- * @param string $fieldName The name of the form field.
267
- * @param array $rules Validation rules for the field.
268
- * @return string HTML attributes for the field.
269
- */
270
- public function register($fieldName, $rules = []): string
271
- {
272
- $value = Validator::string($this->data[$fieldName] ?? '');
273
-
274
- $isTypeButton = array_key_exists('button', $rules);
275
- $attributes = "";
276
- if ($isTypeButton) {
277
- $attributes = "id='fh-$fieldName' name='$fieldName' data-rules='" . json_encode($rules) . "'";
278
- } else {
279
- $attributes = "id='fh-$fieldName' name='$fieldName' value='$value' data-rules='" . json_encode($rules) . "'";
280
- }
281
-
282
- if (!array_intersect(array_keys($rules), ['text', 'email', 'password', 'number', 'date', 'color', 'range', 'tel', 'url', 'search', 'time', 'datetime-local', 'month', 'week', 'file', 'submit', 'checkbox', 'radio', 'hidden', 'button', 'reset'])) {
283
- $rules['text'] = ['value' => true];
284
- }
285
-
286
- foreach ($rules as $rule => $ruleValue) {
287
- $attributes .= $this->parseRule($rule, $ruleValue);
288
- }
289
-
290
- $inputField = $this->stateManager->getState(self::FORM_INPUT_REGISTER) ?? [];
291
- $inputField[$fieldName] = [
292
- 'value' => $value,
293
- 'attributes' => $attributes,
294
- 'rules' => $rules,
295
- ];
296
- $this->stateManager->setState([self::FORM_INPUT_REGISTER => $inputField], true);
297
-
298
- return $attributes;
299
- }
300
-
301
- /**
302
- * Retrieves the registered form fields.
303
- *
304
- * @return array An associative array of registered form fields.
305
- *
306
- * @example
307
- * $form->getRegisteredFields();
308
- * This will return an array of registered form fields.
309
- */
310
- public function getRegisteredFields(): array
311
- {
312
- return $this->stateManager->getState(self::FORM_INPUT_REGISTER) ?? [];
313
- }
314
-
315
- private function parseRule($rule, $ruleValue)
316
- {
317
- $attribute = '';
318
- $ruleParam = $ruleValue;
319
- $ruleParam = is_array($ruleValue) ? $ruleValue['value'] : $ruleValue;
320
-
321
- switch ($rule) {
322
- case 'text':
323
- case 'search':
324
- case 'email':
325
- case 'password':
326
- case 'number':
327
- case 'date':
328
- case 'color':
329
- case 'range':
330
- case 'tel':
331
- case 'url':
332
- case 'time':
333
- case 'datetime-local':
334
- case 'month':
335
- case 'week':
336
- case 'file':
337
- case 'submit':
338
- case "checkbox":
339
- case "radio":
340
- case "hidden":
341
- case "button":
342
- case "reset":
343
- $attribute .= " type='$rule'";
344
- break;
345
- case 'required':
346
- $attribute .= " required";
347
- break;
348
- case 'min':
349
- case 'max':
350
- $attribute .= " $rule='$ruleParam'";
351
- break;
352
- case 'minLength':
353
- case 'maxLength':
354
- $attribute .= " $rule='$ruleParam'";
355
- break;
356
- case 'pattern':
357
- $attribute .= " pattern='$ruleParam'";
358
- break;
359
- case 'autocomplete':
360
- $attribute .= " autocomplete='$ruleParam'";
361
- break;
362
- case 'readonly':
363
- $attribute .= " readonly";
364
- break;
365
- case 'disabled':
366
- $attribute .= " disabled";
367
- break;
368
- case 'placeholder':
369
- $attribute .= " placeholder='$ruleParam'";
370
- break;
371
- case 'autofocus':
372
- $attribute .= " autofocus";
373
- break;
374
- case 'multiple':
375
- $attribute .= " multiple";
376
- break;
377
- case 'accept':
378
- $attribute .= " accept='$ruleParam'";
379
- break;
380
- case 'size':
381
- $attribute .= " size='$ruleParam'";
382
- break;
383
- case 'step':
384
- $attribute .= " step='$ruleParam'";
385
- break;
386
- case 'list':
387
- $attribute .= " list='$ruleParam'";
388
- break;
389
- default:
390
- // Optionally handle unknown rules or log them
391
- break;
392
- }
393
- return $attribute;
394
- }
395
-
396
- /**
397
- * Creates a watch element for a form field.
398
- *
399
- * This function returns an HTML string for a watch element with a unique `id` attribute,
400
- * useful for monitoring changes in the value of a form field.
401
- *
402
- * @param string $field The name of the field to create a watch element for.
403
- *
404
- * @return string An HTML string representing the watch element. The element will have a unique `id` attribute prefixed with "fh-watch-" and suffixed by the field name, and it will include `data-watch-value` and `data-type` attributes.
405
- *
406
- * @example
407
- * Example usage to create a watch element for a "username" field:
408
- *
409
- * echo $form->watch('username');
410
- * Output: "<div id='fh-watch-username' data-watch-value='{value}' data-type='watch'></div>"
411
- */
412
- public function watch(string $field)
413
- {
414
- $field = Validator::string($field);
415
- $fieldData = $this->data[$field] ?? '';
416
- return "id='fh-watch-$field' data-watch-value='$fieldData' data-type='watch'";
417
- }
418
- }
419
-
420
- ?>
421
-
422
- <script>
423
- if (typeof FormHandler === 'undefined') {
424
- class FormHandler {
425
- constructor() {
426
- this.errors = [];
427
- this.dataRulesElements = document.querySelectorAll('[data-rules]');
428
- this.init();
429
- }
430
-
431
- init() {
432
- this.dataRulesElements.forEach(fieldElement => {
433
- this.initializeFieldFromDOM(fieldElement);
434
- });
435
- }
436
-
437
- initializeFieldFromDOM(fieldElement) {
438
- if (!fieldElement) return;
439
-
440
- const fieldName = fieldElement.name;
441
- const rules = JSON.parse(fieldElement.getAttribute('data-rules') || '{}');
442
-
443
- const errors = this.validateField(fieldElement, fieldElement.value, rules);
444
- const errorContainer = document.getElementById(`fh-error-${fieldElement.name}`);
445
- if (errorContainer) {
446
- if (errorContainer.dataset.errorMessage) {
447
- errorContainer.textContent = errors.join(', ');
448
- }
449
- }
450
-
451
- const immediateObserver = (e) => {
452
- const target = e.target;
453
- this.watch(target);
454
-
455
- const errors = this.validateField(target, target.value, rules);
456
- const errorContainer = document.getElementById(`fh-error-${target.name}`);
457
- if (errorContainer) {
458
- errorContainer.textContent = errors.join(', ');
459
- }
460
- };
461
-
462
- fieldElement.addEventListener('input', immediateObserver);
463
- }
464
-
465
- updateElementDisplay(displayElement, field) {
466
- const tagName = field.tagName.toUpperCase();
467
- if (tagName === 'INPUT' || tagName === 'TEXTAREA') {
468
- if (displayElement.tagName === 'INPUT' || displayElement.tagName === 'TEXTAREA') {
469
- displayElement.value = field.value;
470
- } else {
471
- displayElement.dataset.watchValue = field.value;
472
- displayElement.textContent = field.value;
473
- }
474
- } else {
475
- displayElement.textContent = field.textContent;
476
- }
477
- }
478
-
479
- watch(field) {
480
- if (!field) return;
481
-
482
- const watchElement = document.getElementById(`fh-watch-${field.name}`);
483
- if (watchElement) {
484
- this.updateElementDisplay(watchElement, field);
485
- }
486
- }
487
-
488
- clearErrors() {
489
- const errorElements = document.querySelectorAll('[id^="fh-error-"]');
490
- errorElements.forEach(element => {
491
- element.textContent = '';
492
- });
493
-
494
- this.errors = [];
495
- }
496
-
497
- getErrors(field) {
498
- if (field) {
499
- return document.getElementById(`fh-error-${field}`).textContent;
500
- } else {
501
- return this.errors;
502
- }
503
- }
504
-
505
- validateField(field, value, rules) {
506
- if (!rules) return [];
507
- this.errors = [];
508
-
509
- for (const [rule, options] of Object.entries(rules)) {
510
- let ruleValue = options;
511
- let customMessage = null;
512
-
513
- if (typeof options === 'object') {
514
- ruleValue = options.value;
515
- customMessage = options.message || null;
516
- }
517
-
518
- switch (rule) {
519
- case 'text':
520
- case 'search':
521
- if (typeof value !== 'string') {
522
- this.errors.push(customMessage || 'Must be a string.');
523
- }
524
- break;
525
- case 'email':
526
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
527
- this.errors.push(customMessage || 'Invalid email format.');
528
- }
529
- break;
530
- case 'number':
531
- if (isNaN(value)) {
532
- this.errors.push(customMessage || 'Must be a number.');
533
- }
534
- break;
535
- case 'date':
536
- if (isNaN(Date.parse(value))) {
537
- this.errors.push(customMessage || 'Invalid date format.');
538
- }
539
- break;
540
- case 'range':
541
- const [min, max] = ruleValue;
542
- if (isNaN(value) || value < min || value > max) {
543
- this.errors.push(customMessage || `Must be between ${min} and ${max}.`);
544
- }
545
- break;
546
- case 'url':
547
- try {
548
- new URL(value);
549
- } catch (e) {
550
- this.errors.push(customMessage || 'Invalid URL format.');
551
- }
552
- break;
553
- case 'required':
554
- if (!value) {
555
- this.errors.push(customMessage || 'This field is required.');
556
- }
557
- break;
558
- case 'min':
559
- if (Number(value) < ruleValue) {
560
- this.errors.push(customMessage || `Must be at least ${ruleValue}.`);
561
- }
562
- break;
563
- case 'max':
564
- if (Number(value) > ruleValue) {
565
- this.errors.push(customMessage || `Must be at most ${ruleValue}.`);
566
- }
567
- break;
568
- case 'minLength':
569
- if (value.length < ruleValue) {
570
- this.errors.push(customMessage || `Must be at least ${ruleValue} characters.`);
571
- }
572
- break;
573
- case 'maxLength':
574
- if (value.length > ruleValue) {
575
- this.errors.push(customMessage || `Must be at most ${ruleValue} characters.`);
576
- }
577
- break;
578
- case 'pattern':
579
- if (!new RegExp(ruleValue).test(value)) {
580
- this.errors.push(customMessage || 'Invalid format.');
581
- }
582
- break;
583
- case 'accept':
584
- if (!ruleValue.split(',').includes(value)) {
585
- this.errors.push(customMessage || 'Invalid file format.');
586
- }
587
- break;
588
- default:
589
- // Optionally handle unknown rules or log them
590
- break;
591
- }
592
- }
593
-
594
- return this.errors;
595
- }
596
- }
597
-
598
- let formHandler = FormHandler ? new FormHandler() : null;
599
- // Initialize FormHandler on initial page load
600
- document.addEventListener('DOMContentLoaded', function() {
601
- formHandler = new FormHandler();
602
- });
603
- }
604
- </script>