{{-- The opening
tag of a data-entry form, built by spatie/laravel-html instead of hand-written markup plus @csrf/@method. ...fields... Props: id DOM id of the form. Required — jsvalidation binds by selector (#nrems_project_form) and the module tests assert on it, so a form without an id is always a mistake here. action Target url. Required. method Logical verb: POST (default), PUT, PATCH or DELETE. The tag itself always posts; PUT/PATCH/DELETE additionally emit the _method hidden field Laravel spoofs from. Case insensitive. Anything else is an InvalidArgumentException rather than a silently wrong form — GET has no place here (see the boundary below) and a typo'd verb would otherwise render as a plain POST that hits the wrong route. multipart true adds enctype="multipart/form-data". Off by default — only the forms carrying an need it. novalidate On by default: jsvalidation owns the client-side messages, so the browser's own bubbles are turned off. Pass :novalidate="false" for a form with no fields to validate (a logout button, a delete confirmation) so the rendered markup keeps saying what it means. A CSRF token is emitted unconditionally — there is no opt-out, because every form this component is for is a state change. One difference from Laravel's @csrf: spatie's html()->token() does not set autocomplete="off" on the hidden input, so a browser restoring the form from bfcache on a back navigation can put a stale token back into a field the server will reject — a sporadic 419 after Back. Reaching that attribute means dropping html()->form() for Form::create() with hand-built children; recorded here as the option, not taken. Everything else — class, data-*, onsubmit, autocomplete — passes straight through to the . Boundary: this component is for POST-family *data-entry* forms only. The index filter bars (projects, units, unit_types) are GET-less client-side filters — wrappers that never reach the server and must not carry a CSRF token or a method — and stay plain HTML. --- spatie/laravel-html variant: the tag is html()->form($method, $action), which is what emits the CSRF token, and the component's own attribute bag is merged onto it the same way the field components do it. Only open() is rendered here; emits the closing tag. --}} @props([ 'id', 'action', 'method' => 'POST', 'multipart' => false, 'novalidate' => true, ]) @php $verb = strtoupper((string) $method); if (! in_array($verb, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { throw new InvalidArgumentException( "x-form.open: unsupported method [{$verb}]. Use POST, PUT, PATCH or DELETE — ". 'a GET form is not a data-entry form and stays plain HTML.' ); } $spoofed = $verb !== 'POST'; // The spoof field is added here rather than left to html()->form($verb), // which builds it through html()->hidden() — and that gives every input an // id derived from its name, so a page carrying two spoofed forms (any // project with wings) would render two elements with id="_method". Asking // html()->form() for POST and appending an id-less hidden reproduces // exactly what @method() emitted, and still leaves the CSRF token to // html()->form(). Child order stays token-then-_method, as it was. $form = html()->form('POST', (string) $action) ->id($id) ->novalidate((bool) $novalidate) ->acceptsFilesIf((bool) $multipart); if ($spoofed) { $form = $form->addChild( html()->hidden('_method')->value($verb)->forgetAttribute('id') ); } $passthrough = form_passthrough_attributes($attributes); $form = $form->attributes($passthrough); @endphp {!! $form->open() !!}