@formique/semantq 1.0.4 → 1.0.5

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.
Files changed (3) hide show
  1. package/README.md +17 -12
  2. package/formique-semantq.js +553 -393
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,7 +42,7 @@ Formique Semantq is a native Semantq JS framework Schema Defintion Language (SDL
42
42
  - **Mobile Responsive**: Forms are mobile responsive out of the box.
43
43
  - **Nested Dynamic Conditional Logic**: Implement complex conditional logic to show or hide form fields based on user input.
44
44
  - **Dynamic Dropdowns**: Create dropdowns whose options change dynamically based on other field selections.
45
- - **JavaScript-Driven Themes**: Apply themes dynamically using JavaScript for a customizable user interface.
45
+ - **JavaScript-Driven Themes**: Apply themes or theme colors dynamically using JavaScript for a customizable user interface.
46
46
  - **WAI-ARIA and WCAG-Compliant HTML**: Ensure all form elements are accessible and meet WCAG standards.
47
47
  - **Progressive Enhancement**: Forms function with or without JavaScript, ensuring accessibility and functionality across all environments.
48
48
 
@@ -60,14 +60,6 @@ npx sv create
60
60
  npx sv create my-app
61
61
  ```
62
62
 
63
- #### Select the following options:
64
-
65
- - **SemantqKit minimal** (optional but preferred)
66
- - **Type checking with TypeScript** (optional but preferred)
67
- - **ESLint** (optional but preferred)
68
- - **npm** (required)
69
-
70
-
71
63
  > **Note:** Always refer to the latest official Semantq guide on how to create a Semantq app, as this may change. [Semantq Documentation: Creating a Project](https://Semantq.dev/docs/kit/creating-a-project)
72
64
 
73
65
 
@@ -77,7 +69,7 @@ npx sv create my-app
77
69
  npm run dev
78
70
 
79
71
  # or start the server and open the app in a new browser tab
80
- npm run dev -- --open
72
+ npm run dev
81
73
  ```
82
74
 
83
75
 
@@ -101,12 +93,25 @@ For demo purposes, let's create a new route (page) in `src/routes/registration`.
101
93
 
102
94
  ## Step 2: Add the CSS (Optional)
103
95
 
104
- Paste the following Formique CSS in the `<head>` section of `src/app.html`:
96
+ The NPM option:
97
+
98
+ ```javascript
99
+ npm i formique-css
100
+ ```
101
+
102
+ ```javascript
103
+ import 'formique-css';
104
+ ```
105
+
106
+ The CDN Option:
107
+
108
+ Include this inside the @head .. @end block of page layout file: `@layout.smq`
105
109
 
106
110
  ```html
107
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/formique-css@1.0.7/formique.min.css" formique-style>
111
+ <link rel="stylesheet" href="https://unpkg.com/formique-css@1.0.11/formique-css.css" />
108
112
  ```
109
113
 
114
+
110
115
  **Note:** The provided Formique CSS is optional. Formique will function fully without it, allowing you full flexibility to apply your own styles. However, for convenience, a set of default class names is available to help you quickly style form containers, form elements, and input types. See the sections below for a complete list of available class names.
111
116
 
112
117
  ## Step 3: Install `@formique/semantq`
@@ -50,126 +50,122 @@ class FormBuilder
50
50
 
51
51
  // Extended class for specific form rendering methods
52
52
  class Formique extends FormBuilder {
53
- constructor(formSchema, formSettings = {}, formParams = {}, ) {
54
- super();
55
- this.formSchema = formSchema;
56
- this.formParams = formParams;
57
- this.formSettings = {
58
- requiredFieldIndicator: true,
59
- placeholders: true,
60
- asteriskHtml: '<span aria-hidden="true" style="color: red;">*</span>',
61
- ...formSettings
62
- };
63
- this.divClass = 'input-block';
64
- this.inputClass = 'form-input';
65
- this.radioGroupClass = 'radio-group';
66
- this.checkboxGroupClass = 'checkbox-group';
67
- this.selectGroupClass = 'form-select';
68
- this.submitButtonClass = 'form-submit-btn';
69
- this.formContainerId = formSettings?.formContainerId || 'formique';
70
- this.formContainerStyle = formSettings?.formContainerStyle || null;
71
- this.formId = this.formParams?.id || this.generateFormId();
72
- //console.log(this.formId);
73
- this.formAction = formParams?.action || 'https://httpbin.org/post';
74
- this.method = 'POST';
75
- this.formMarkUp = '';
76
- this.dependencyGraph = {};
77
- this.redirect = formSettings?.redirect ||'';
78
- this.redirectURL = formSettings?.redirectURL ||'';
79
- this.activeTheme = formSettings.theme || null;
80
- this.themeColor = formSettings.themeColor || null;
81
- this.themeColorMap = {
82
- 'primary': {
83
- '--formique-base-bg': '#ffffff',
84
- '--formique-base-text': '#333333',
85
- '--formique-base-shadow': '0 10px 30px rgba(0, 0, 0, 0.1)',
86
- '--formique-base-label': '#555555',
87
- '--formique-input-border': '#dddddd',
88
- '--formique-focus-color': null, // Will be set to themeColor
89
- '--formique-btn-bg': null, // Will be set to themeColor
90
- '--formique-btn-text': '#ffffff',
91
- '--formique-btn-shadow': null // Will be calculated from themeColor
92
- }
93
- };
94
-
95
-
96
- this.themes = [
97
- "dark",
98
- "light",
99
- "pink",
100
- "light",
101
- "indigo",
102
- "dark-blue",
103
- "light-blue",
104
- "dark-orange",
105
- "bright-yellow",
106
- "green",
107
- "purple",
108
- "midnight-blush",
109
- "deep-blue",
110
- "blue",
111
- "brown",
112
- "orange"
113
- ];
114
-
115
- //this.formiqueEndpoint = "http://localhost:3000/api/send-email";
116
- this.formiqueEndpoint = "https://formiqueapi.onrender.com/api/send-email";
117
-
118
- // DISABLE EVENT LISTENER
119
- // document.addEventListener('DOMContentLoaded', () => {
120
-
121
- /*
122
- if (this.formParams && Object.keys(this.formParams).length > 0) {
123
- this.formMarkUp += this.renderFormElement();
124
- } */
125
-
126
- this.formMarkUp += this.renderFormElement();
127
-
128
-
129
- this.renderForm();
130
- this.renderFormHTML();
131
- this.initDependencyGraph();
132
- this.registerObservers();
133
-
134
-
135
- if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
136
- let theme = this.formSettings.theme;
137
- this.applyTheme(theme, this.formContainerId);
138
- } else {
139
- // Fallback to dark theme if no theme is set or invalid theme
140
- this.applyTheme('dark', this.formContainerId);
141
- }
142
-
143
- document.getElementById(`${this.formId}`).addEventListener('submit', function(event) {
144
-
145
- if (this.formSettings.submitMode === 'email') {
146
- event.preventDefault(); // Prevent the default form submission
147
- document.getElementById("formiqueSpinner").style.display = "block";
148
- //return;
149
- this.handleEmailSubmission(this.formId);
150
- }
151
-
152
-
153
- if (this.formSettings.submitOnPage) {
154
- event.preventDefault(); // Prevent the default form submission
155
- document.getElementById("formiqueSpinner").style.display = "block";
156
- this.handleOnPageFormSubmission(this.formId);
157
- //console.warn("listener fired at least>>", this.formParams.id, this.method);
158
- }
159
- }.bind(this)); // Bind `this` to ensure it's correct inside the event listener
160
-
53
+ constructor(formSchema, formSettings = {}, formParams = {}) {
54
+ super();
55
+ this.formSchema = formSchema;
56
+ this.formParams = formParams;
57
+ this.formSettings = {
58
+ requiredFieldIndicator: true,
59
+ placeholders: true,
60
+ asteriskHtml: '<span aria-hidden="true" style="color: red;">*</span>',
61
+ ...formSettings
62
+ };
63
+ this.divClass = 'input-block';
64
+ this.inputClass = 'form-input';
65
+ this.radioGroupClass = 'radio-group';
66
+ this.checkboxGroupClass = 'checkbox-group';
67
+ this.selectGroupClass = 'form-select';
68
+ this.submitButtonClass = 'form-submit-btn';
69
+ this.formContainerId = formSettings?.formContainerId || 'formique';
70
+ // Ensure formParams.id is used if provided, otherwise generate a new ID
71
+ this.formId = this.formParams?.id || this.generateFormId();
72
+ this.formAction = formParams?.action || 'https://httpbin.org/post';
73
+ this.method = 'POST';
74
+ this.formMarkUp = '';
75
+ this.dependencyGraph = {};
76
+ this.redirect = formSettings?.redirect || '';
77
+ this.redirectURL = formSettings?.redirectURL || '';
78
+ this.themes = [
79
+ "dark", "light", "pink", "indigo", "dark-blue", "light-blue",
80
+ "dark-orange", "bright-yellow", "green", "purple", "midnight-blush",
81
+ "deep-blue", "blue", "brown", "orange"
82
+ ];
83
+ this.formiqueEndpoint = "https://formiqueapi.onrender.com/api/send-email";
84
+
85
+
86
+ document.addEventListener('DOMContentLoaded', () => {
87
+ // 1. Build the form's HTML in memory
88
+ this.formMarkUp += this.renderFormElement(); // Adds opening <form> tag and any hidden inputs
89
+
90
+ // Filter out 'submit' field for rendering, and render all other fields
91
+ const nonSubmitFieldsHtml = this.formSchema
92
+ .filter(field => field[0] !== 'submit')
93
+ .map(field => {
94
+ const [type, name, label, validate, attributes = {}, options] = field;
95
+ return this.renderField(type, name, label, validate, attributes, options);
96
+ }).join('');
97
+ this.formMarkUp += nonSubmitFieldsHtml;
98
+
99
+ // Find and render the submit button separately, at the very end of the form content
100
+ const submitField = this.formSchema.find(field => field[0] === 'submit');
101
+ if (submitField) {
102
+ const [type, name, label, validate, attributes = {}] = submitField;
103
+ const id = attributes.id || name;
104
+ let buttonClass = this.submitButtonClass;
105
+ if ('class' in attributes) {
106
+ buttonClass = attributes.class;
107
+ }
108
+ let additionalAttrs = '';
109
+ for (const [key, value] of Object.entries(attributes)) {
110
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
111
+ if (key.startsWith('on')) {
112
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
113
+ additionalAttrs += ` ${key}="${eventValue}"`;
114
+ } else {
115
+ if (value === true) {
116
+ additionalAttrs += ` ${key.replace(/_/g, '-')}`;
117
+ } else if (value !== false) {
118
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ this.formMarkUp += `
124
+ <div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
125
+ <div class="formique-spinner"></div>
126
+ <p class="message">Hang in tight, we are submitting your details…</p>
127
+ </div>
128
+ <input type="submit" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>
129
+ `;
130
+ }
161
131
 
162
132
 
163
- if (this.formContainerStyle) {
164
- const formContainer = document.getElementById(this.formContainerId);
165
- formContainer.setAttribute("style", this.formContainerStyle);
166
- }
133
+ // 2. Inject the complete form HTML into the DOM
134
+ this.renderFormHTML(); // This puts the form element into the document!
167
135
 
136
+ // 3. Now that the form is in the DOM, attach event listeners
137
+ const formElement = document.getElementById(`${this.formId}`);
138
+ if (formElement) { // Add a check here just in case, although it should now exist
139
+ formElement.addEventListener('submit', function(event) {
140
+ if (this.formSettings.submitMode === 'email') {
141
+ event.preventDefault();
142
+ document.getElementById("formiqueSpinner").style.display = "block";
143
+ this.handleEmailSubmission(this.formId);
144
+ }
168
145
 
146
+ if (this.formSettings.submitOnPage) {
147
+ event.preventDefault();
148
+ document.getElementById("formiqueSpinner").style.display = "block";
149
+ this.handleOnPageFormSubmission(this.formId);
150
+ }
151
+ }.bind(this));
152
+ } else {
153
+ console.error(`Form with ID ${this.formId} not found after rendering. Event listener could not be attached.`);
154
+ }
169
155
 
170
- // disable wrapper for DOM event listener
171
- // });
156
+ // Initialize dependency graph and observers after the form is rendered
157
+ this.initDependencyGraph();
158
+ this.registerObservers();
172
159
 
160
+ // Apply theme
161
+ if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
162
+ let theme = this.formSettings.theme;
163
+ this.applyTheme(theme, this.formContainerId);
164
+ } else {
165
+ this.applyTheme('dark', this.formContainerId);
166
+ }
167
+ }); // DOM LISTENER WRAPPER
168
+
173
169
  // CONSTRUCTOR WRAPPER FOR FORMIQUE CLASS
174
170
  }
175
171
 
@@ -346,107 +342,56 @@ registerObservers() {
346
342
 
347
343
 
348
344
  applyTheme(theme, formContainerId) {
349
- const formContainer = document.getElementById(formContainerId);
350
- const spinnerContainer = document.getElementById('formiqueSpinner');
345
+ //const stylesheet = document.querySelector('link[formique-style]');
351
346
 
352
- if (!formContainer) {
353
- console.error(`Form container with ID ${formContainerId} not found.`);
354
- return;
355
- }
347
+ const stylesheet = document.querySelector('link[href*="formique-css"]');
348
+
349
+ if (!stylesheet) {
350
+ console.error("Stylesheet with 'formique-css' in the name not found!");
351
+ return;
352
+ }
356
353
 
357
- // Clear any existing theme classes
358
- this.themes.forEach(t => formContainer.classList.remove(`${t}-theme`));
359
- formContainer.classList.remove('custom-theme');
360
- spinnerContainer.classList.remove('custom-theme');
354
+ fetch(stylesheet.href)
355
+ .then(response => response.text())
356
+ .then(cssText => {
357
+ // Extract theme-specific CSS rules
358
+ const themeRules = cssText.match(new RegExp(`\\.${theme}-theme\\s*{([^}]*)}`, 'i'));
361
359
 
362
- // If themeColor is provided, use it to create a custom theme
363
- if (this.themeColor) {
364
- this.applyCustomTheme(formContainerId);
360
+ if (!themeRules) {
361
+ console.error(`Theme rules for ${theme} not found in the stylesheet.`);
365
362
  return;
366
- }
367
-
368
- // Fall back to predefined theme if no themeColor
369
- const stylesheet = document.querySelector('link[href*="formique-css"]');
370
- if (!stylesheet) {
371
- console.error("Stylesheet with 'formique-css' in the name not found!");
372
- return;
373
- }
374
-
375
- fetch(stylesheet.href)
376
- .then(response => response.text())
377
- .then(cssText => {
378
- const themeRules = cssText.match(new RegExp(`\\.${theme}-theme\\s*{([^}]*)}`, 'i'));
379
- if (!themeRules) {
380
- console.error(`Theme rules for ${theme} not found in the stylesheet.`);
381
- return;
382
- }
383
-
384
- const themeCSS = themeRules[1].trim();
385
- formContainer.classList.add(`${theme}-theme`, 'formique');
386
- spinnerContainer.classList.add(`${theme}-theme`);
387
-
388
- const clonedStyle = document.createElement('style');
389
- clonedStyle.textContent = `#${formContainerId} { ${themeCSS} }`;
390
- formContainer.parentNode.insertBefore(clonedStyle, formContainer);
391
- })
392
- .catch(error => {
393
- console.error('Error loading the stylesheet:', error);
394
- });
395
- }
363
+ }
396
364
 
397
- applyCustomTheme(formContainerId) {
398
- const formContainer = document.getElementById(formContainerId);
399
- const spinnerContainer = document.getElementById('formiqueSpinner');
365
+ // Extract CSS rules for the theme
366
+ const themeCSS = themeRules[1].trim();
400
367
 
401
- if (!formContainer) return;
368
+ // Find the form container element
369
+ const formContainer = document.getElementById(formContainerId);
402
370
 
403
- formContainer.classList.add('custom-theme', 'formique');
404
- spinnerContainer.classList.add('custom-theme');
371
+ if (formContainer) {
372
+ // Append the theme class to the form container
373
+ formContainer.classList.add(`${theme}-theme`, 'formique');
405
374
 
406
- this.activeTheme = 'custom-theme';
407
375
 
408
- // Calculate shadow color (semi-transparent themeColor)
409
- const shadowColor = this.hexToRgbA(this.themeColor, 0.3);
410
-
411
- // Create custom theme CSS variables
412
- const customTheme = {
413
- ...this.themeColorMap.primary,
414
- '--formique-focus-color': this.themeColor,
415
- '--formique-btn-bg': this.themeColor,
416
- '--formique-btn-shadow': `0 2px 10px ${shadowColor}`
417
- };
418
-
419
- // Apply the styles
420
- const styleElement = document.createElement('style');
421
- styleElement.textContent = `
422
- #${formContainerId} {
423
- ${Object.entries(customTheme)
424
- .map(([varName, value]) =>
425
- value ? `${varName}: ${value};` : ''
426
- )
427
- .join('\n')}
428
- }
429
- `;
376
+ // Create a <style> tag with the extracted theme styles
377
+ const clonedStyle = document.createElement('style');
378
+ clonedStyle.textContent = `
379
+ #${formContainerId} {
380
+ ${themeCSS}
381
+ }
382
+ `;
430
383
 
431
- // Remove any existing custom style
432
- const existingStyle = document.querySelector(`style[data-custom-theme="${formContainerId}"]`);
433
- if (existingStyle) existingStyle.remove();
434
-
435
- styleElement.setAttribute('data-custom-theme', formContainerId);
436
- formContainer.parentNode.insertBefore(styleElement, formContainer);
437
- }
384
+ // Insert the <style> tag above the form container
385
+ formContainer.parentNode.insertBefore(clonedStyle, formContainer);
438
386
 
439
- hexToRgbA(hex, alpha) {
440
- let c;
441
- if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
442
- c = hex.substring(1).split('');
443
- if (c.length === 3) {
444
- c = [c[0], c[0], c[1], c[1], c[2], c[2]];
445
- }
446
- c = '0x' + c.join('');
447
- return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')},${alpha})`;
448
- }
449
- return `rgba(0, 0, 0, ${alpha})`;
387
+ // console.log(`Applied ${theme} theme to form container: ${formContainerId}`);
388
+ } else {
389
+ console.error(`Form container with ID ${formContainerId} not found.`);
390
+ }
391
+ })
392
+ .catch(error => {
393
+ console.error('Error loading the stylesheet:', error);
394
+ });
450
395
  }
451
396
 
452
397
 
@@ -500,6 +445,7 @@ Object.keys(paramsToUse).forEach(key => {
500
445
 
501
446
 
502
447
  // Main renderForm method
448
+ /*
503
449
  renderForm() {
504
450
  // Process each field synchronously
505
451
  const formHTML = this.formSchema.map(field => {
@@ -508,8 +454,61 @@ renderForm() {
508
454
  }).join('');
509
455
  this.formMarkUp += formHTML;
510
456
  }
457
+ */
511
458
 
459
+ renderForm() {
460
+ // Filter out the 'submit' type before mapping
461
+ const formHTML = this.formSchema
462
+ .filter(field => field[0] !== 'submit') // Exclude submit button from this loop
463
+ .map(field => {
464
+ const [type, name, label, validate, attributes = {}, options] = field;
465
+ return this.renderField(type, name, label, validate, attributes, options);
466
+ }).join('');
467
+ this.formMarkUp += formHTML;
468
+ }
469
+
470
+
471
+
472
+ // New method to render the submit button specifically
473
+ renderSubmitButtonElement() {
474
+ const submitField = this.formSchema.find(field => field[0] === 'submit');
475
+ if (submitField) {
476
+ const [type, name, label, validate, attributes = {}] = submitField;
477
+ const id = attributes.id || name;
478
+ let buttonClass = this.submitButtonClass;
479
+ if ('class' in attributes) {
480
+ buttonClass = attributes.class;
481
+ }
482
+ let additionalAttrs = '';
483
+ for (const [key, value] of Object.entries(attributes)) {
484
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
485
+ if (key.startsWith('on')) {
486
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
487
+ additionalAttrs += ` ${key}="${eventValue}"`;
488
+ } else {
489
+ if (value === true) {
490
+ additionalAttrs += ` ${key.replace(/_/g, '-')}`;
491
+ } else if (value !== false) {
492
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
493
+ }
494
+ }
495
+ }
496
+ }
512
497
 
498
+ // Include the spinner div before the submit button
499
+ return `
500
+ <div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
501
+ <div class="formique-spinner"></div>
502
+ <p class="message">Hang in tight, we are submitting your details…</p>
503
+ </div>
504
+ <input type="submit" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>
505
+ `.trim();
506
+ }
507
+ return ''; // Return empty string if no submit button is found in schema
508
+ }
509
+
510
+
511
+ /*
513
512
  renderField(type, name, label, validate, attributes, options) {
514
513
  const fieldRenderMap = {
515
514
  'text': this.renderTextField,
@@ -549,13 +548,93 @@ renderField(type, name, label, validate, attributes, options) {
549
548
  }
550
549
  }
551
550
 
551
+ */
552
+
553
+
554
+ // renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
555
+ renderField(type, name, label, validate, attributes, options) {
556
+ const fieldRenderMap = {
557
+ 'text': this.renderTextField,
558
+ 'email': this.renderEmailField,
559
+ 'number': this.renderNumberField,
560
+ 'password': this.renderPasswordField,
561
+ 'textarea': this.renderTextAreaField,
562
+ 'tel': this.renderTelField,
563
+ 'date': this.renderDateField,
564
+ 'time': this.renderTimeField,
565
+ 'datetime-local': this.renderDateTimeField,
566
+ 'month': this.renderMonthField,
567
+ 'week': this.renderWeekField,
568
+ 'url': this.renderUrlField,
569
+ 'search': this.renderSearchField,
570
+ 'color': this.renderColorField,
571
+ 'checkbox': this.renderCheckboxField,
572
+ 'radio': this.renderRadioField,
573
+ 'file': this.renderFileField,
574
+ 'hidden': this.renderHiddenField,
575
+ 'image': this.renderImageField,
576
+ 'singleSelect': this.renderSingleSelectField,
577
+ 'multipleSelect': this.renderMultipleSelectField,
578
+ 'dynamicSingleSelect': this.renderDynamicSingleSelectField,
579
+ 'range': this.renderRangeField,
580
+ 'submit': this.renderSubmitButton, // Keep this for completeness, but renderSubmitButtonElement will now handle it
581
+ };
582
+
583
+ const renderMethod = fieldRenderMap[type];
584
+
585
+ if (renderMethod) {
586
+ // If the type is 'submit', ensure we use the specific renderSubmitButtonElement
587
+ // Although, with the filter in renderForm(), this branch for 'submit' type
588
+ // might not be hit in the primary rendering flow, it's good practice.
589
+ return renderMethod.call(this, type, name, label, validate, attributes, options);
590
+
591
+ if (type === 'submit') {
592
+ return this.renderSubmitButton(type, name, label, validate, attributes, options);
593
+ }
594
+ //return renderMethod.call(this, type, name, label, validate, attributes, options);
595
+ } else {
596
+ console.warn(`Unsupported field type '${type}' encountered.`);
597
+ return '';
598
+ }
599
+ }
600
+
601
+
602
+
603
+ renderSubmitButton(type, name, label, validate, attributes) {
604
+ // This method can simply call the dedicated submit button renderer if it's kept separate.
605
+ // Or, if renderField is only used for non-submit fields, this method might not be strictly necessary
606
+ // to be called from renderField's map, but it needs to exist if mapped.
607
+ // For simplicity, I'll make it consistent with the new separation.
608
+ const id = attributes.id || name;
609
+ let buttonClass = this.submitButtonClass;
610
+ if ('class' in attributes) {
611
+ buttonClass = attributes.class;
612
+ }
613
+ let additionalAttrs = '';
614
+ for (const [key, value] of Object.entries(attributes)) {
615
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
616
+ if (key.startsWith('on')) {
617
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
618
+ additionalAttrs += ` ${key}="${eventValue}"`;
619
+ } else {
620
+ if (value === true) {
621
+ additionalAttrs += ` ${key.replace(/_/g, '-')}`;
622
+ } else if (value !== false) {
623
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"`;
624
+ }
625
+ }
626
+ }
627
+ }
628
+ // No spinner div here, as that's added once by renderSubmitButtonElement
629
+ return `<input type="${type}" id="${id}" class="${buttonClass}" value="${label}"${additionalAttrs}>`;
630
+ }
552
631
 
553
632
 
554
633
  // Show success/error messages (externalizable)
555
634
  showSuccessMessage(message) {
556
635
  const container = document.getElementById(this.formContainerId);
557
636
  container.innerHTML = `
558
- <div class="formique-success ${this.activeTheme}">${message}</div>
637
+ <div class="formique-success"> ${message}</div>
559
638
  ${this.formSettings.redirectURL
560
639
  ? `<meta http-equiv="refresh" content="2;url=${this.formSettings.redirectURL}">`
561
640
  : ""}
@@ -565,7 +644,7 @@ showSuccessMessage(message) {
565
644
  showErrorMessage(message) {
566
645
  const container = document.getElementById(this.formContainerId);
567
646
  const errorDiv = document.createElement("div");
568
- errorDiv.className = `formique-error ${this.activeTheme}`;
647
+ errorDiv.className = "formique-error";
569
648
  errorDiv.textContent = `${message}`;
570
649
  container.prepend(errorDiv);
571
650
  }
@@ -579,92 +658,120 @@ hasFileInputs(form) {
579
658
 
580
659
 
581
660
 
582
- // A complete function to replace your old one
583
- // Use this function wherever you are currently using the fetch().then()... structure
584
- // This is the complete, final version of the class method.
585
- // It is an async arrow function, so 'this' is automatically correct.
586
- handleEmailSubmission = async (formId) => {
587
- try {
588
- const form = document.getElementById(formId);
589
- if (!form) throw new Error(`Form with ID ${formId} not found`);
590
-
591
- // --- Start of Payload and Method Logic (as provided previously) ---
592
- const payload = {
593
- formData: {},
594
- metadata: {
595
- recipients: this.formSettings.sendTo,
596
- timestamp: new Date().toISOString()
597
- }
598
- };
599
-
600
- let senderEmail = '';
601
- let formSubject = '';
602
-
603
- new FormData(form).forEach((value, key) => {
604
- payload.formData[key] = value;
605
- const lowerKey = key.toLowerCase();
606
- if ((lowerKey === 'email' || lowerKey.includes('email'))) {
607
- senderEmail = value;
608
- }
609
- if ((lowerKey === 'subject' || lowerKey.includes('subject'))) {
610
- formSubject = value;
611
- }
612
- });
661
+ async handleEmailSubmission(formId) {
662
+ console.log(`Starting email submission for form ID: ${formId}`);
663
+
664
+ const form = document.getElementById(formId);
665
+ if (!form) {
666
+ console.error(`Form with ID ${formId} not found`);
667
+ throw new Error(`Form with ID ${formId} not found`);
668
+ }
613
669
 
614
- payload.metadata.subject = formSubject || this.formSettings.subject || 'Message From Contact Form';
615
- if (senderEmail) {
616
- payload.metadata.sender = senderEmail;
617
- payload.metadata.replyTo = senderEmail;
670
+ // Validate required settings - now checks if sendTo is array with at least one item
671
+ if (!Array.isArray(this.formSettings?.sendTo) || this.formSettings.sendTo.length === 0) {
672
+ console.error('formSettings.sendTo must be an array with at least one recipient email');
673
+ throw new Error('formSettings.sendTo must be an array with at least one recipient email');
674
+ }
675
+
676
+ // Serialize form data
677
+ const payload = {
678
+ formData: {},
679
+ metadata: {
680
+ recipients: this.formSettings.sendTo, // Now sending array
681
+ timestamp: new Date().toISOString()
618
682
  }
619
- // --- End of Payload and Method Logic ---
683
+ };
684
+
685
+ let senderName = '';
686
+ let senderEmail = '';
687
+ let formSubject = '';
688
+
689
+ console.log('Initial payload structure:', JSON.parse(JSON.stringify(payload)));
690
+
691
+ // Process form fields (unchanged)
692
+ new FormData(form).forEach((value, key) => {
693
+ console.log(`Processing form field - Key: ${key}, Value: ${value}`);
694
+ payload.formData[key] = value;
695
+
696
+ const lowerKey = key.toLowerCase();
697
+ if ((lowerKey === 'email' || lowerKey.includes('email'))) {
698
+ senderEmail = value;
699
+ }
700
+ if ((lowerKey === 'name' || lowerKey.includes('name'))) {
701
+ senderName = value;
702
+ }
703
+ if ((lowerKey === 'subject' || lowerKey.includes('subject'))) {
704
+ formSubject = value;
705
+ }
706
+ });
707
+
708
+ // Determine the email subject with fallback logic
709
+ payload.metadata.subject = formSubject ||
710
+ this.formSettings.subject ||
711
+ 'Message From Contact Form';
712
+
713
+ console.log('Determined email subject:', payload.metadata.subject);
714
+
715
+ // Add sender information to metadata
716
+ if (senderEmail) {
717
+ payload.metadata.sender = senderEmail;
718
+ payload.metadata.replyTo = senderName
719
+ ? `${senderName} <${senderEmail}>`
720
+ : senderEmail;
721
+ }
722
+
723
+ console.log('Payload after form processing:', JSON.parse(JSON.stringify(payload)));
620
724
 
725
+ try {
621
726
  const endpoint = this.formiqueEndpoint || this.formAction;
622
727
  const method = this.method || 'POST';
623
-
624
- // Show spinner when request starts
625
- document.getElementById("formiqueSpinner").style.display = "flex";
728
+
729
+ console.log(`Preparing to send request to: ${endpoint}`);
730
+ console.log(`Request method: ${method}`);
731
+ console.log('Final payload being sent:', payload);
626
732
 
627
733
  const response = await fetch(endpoint, {
628
734
  method: method,
629
- headers: {
735
+ headers: {
630
736
  'Content-Type': 'application/json',
631
- 'X-Formique-Version': '1.0'
737
+ 'X-Formique-Version': '1.0'
632
738
  },
633
739
  body: JSON.stringify(payload)
634
740
  });
635
741
 
636
- // The core fix: Read the response body as text first.
637
- // This will not fail on an empty response.
638
- const responseBodyText = await response.text();
639
- let data = {};
640
-
641
- // Only try to parse if the body isn't empty.
642
- if (responseBodyText.length > 0) {
643
- try {
644
- data = JSON.parse(responseBodyText);
645
- } catch (err) {
646
- // This handles cases where the server returns non-JSON data.
647
- console.warn("Response was not valid JSON or unexpected format:", err);
648
- }
649
- }
742
+ console.log(`Received response with status: ${response.status}`);
650
743
 
651
744
  if (!response.ok) {
652
- const errorMsg = data.error || `HTTP error! status: ${response.status}`;
653
- throw new Error(errorMsg);
745
+ const errorData = await response.json().catch(() => ({}));
746
+ console.error('API Error Response:', errorData);
747
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
748
+ document.getElementById("formiqueSpinner").style.display = "none";
749
+
654
750
  }
655
751
 
656
- const successMessage = this.formSettings.successMessage || data.message || "Your message has been sent successfully!";
752
+ const data = await response.json();
753
+ console.log('API Success Response:', data);
754
+
755
+ const successMessage = this.formSettings.successMessage ||
756
+ data.message ||
757
+ 'Your message has been sent successfully!';
758
+ console.log(`Showing success message: ${successMessage}`);
759
+
657
760
  this.showSuccessMessage(successMessage);
658
761
 
659
762
  } catch (error) {
660
- console.error("Email submission failed:", error);
661
- const errorMessage = this.formSettings.errorMessage || error.message || "Failed to send message. Please try again later.";
763
+ console.error('Email submission failed:', error);
764
+ const errorMessage = this.formSettings.errorMessage ||
765
+ error.message ||
766
+ 'Failed to send message. Please try again later.';
767
+ console.log(`Showing error message: ${errorMessage}`);
662
768
  this.showErrorMessage(errorMessage);
663
- } finally {
664
- // Ensure spinner is hidden on success or failure
665
769
  document.getElementById("formiqueSpinner").style.display = "none";
770
+
666
771
  }
667
- };
772
+ }
773
+
774
+
668
775
 
669
776
  // Email validation helper
670
777
  validateEmail(email) {
@@ -742,7 +849,7 @@ if (formContainer) {
742
849
  errorMessageDiv.innerHTML = err;
743
850
 
744
851
  // Append the new error message div to the form container
745
- //formContainer.appendChild(errorMessageDiv);
852
+ formContainer.appendChild(errorMessageDiv);
746
853
  }
747
854
  });
748
855
 
@@ -1422,17 +1529,18 @@ const telInputValidationAttributes = [
1422
1529
  }
1423
1530
 
1424
1531
  // Handle the binding syntax
1425
- let bindingDirective = '';
1426
- if (attributes.binding === 'bind:value' && name) {
1427
- bindingDirective = `bind:value="${name}"\n`;
1428
- }
1429
- if (attributes.binding.startsWith('::') && name) {
1430
- bindingDirective = `bind:value="${name}"\n`;
1431
- }
1432
- if (attributes.binding && !name) {
1433
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1434
- return;
1435
- }
1532
+ // Handle the binding syntax
1533
+ let bindingDirective = '';
1534
+ if (attributes?.binding === 'bind:value' && name) {
1535
+ bindingDirective = `bind:value="${name}"\n`;
1536
+ }
1537
+ if (attributes?.binding?.startsWith('::') && name) {
1538
+ bindingDirective = `bind:value="${name}"\n`;
1539
+ }
1540
+ if (attributes?.binding && !name) {
1541
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1542
+ return;
1543
+ }
1436
1544
 
1437
1545
  // Get the id from attributes or fall back to name
1438
1546
  let id = attributes.id || name;
@@ -1543,13 +1651,13 @@ renderDateField(type, name, label, validate, attributes) {
1543
1651
 
1544
1652
  // Handle the binding syntax
1545
1653
  let bindingDirective = '';
1546
- if (attributes.binding === 'bind:value' && name) {
1654
+ if (attributes?.binding === 'bind:value' && name) {
1547
1655
  bindingDirective = `bind:value="${name}"\n`;
1548
1656
  }
1549
- if (attributes.binding.startsWith('::') && name) {
1657
+ if (attributes?.binding?.startsWith('::') && name) {
1550
1658
  bindingDirective = `bind:value="${name}"\n`;
1551
1659
  }
1552
- if (attributes.binding && !name) {
1660
+ if (attributes?.binding && !name) {
1553
1661
  console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1554
1662
  return;
1555
1663
  }
@@ -1663,15 +1771,16 @@ renderTimeField(type, name, label, validate, attributes) {
1663
1771
  });
1664
1772
  }
1665
1773
 
1774
+ // Handle the binding syntax
1666
1775
  // Handle the binding syntax
1667
1776
  let bindingDirective = '';
1668
- if (attributes.binding === 'bind:value' && name) {
1777
+ if (attributes?.binding === 'bind:value' && name) {
1669
1778
  bindingDirective = `bind:value="${name}"\n`;
1670
1779
  }
1671
- if (attributes.binding.startsWith('::') && name) {
1780
+ if (attributes?.binding?.startsWith('::') && name) {
1672
1781
  bindingDirective = `bind:value="${name}"\n`;
1673
1782
  }
1674
- if (attributes.binding && !name) {
1783
+ if (attributes?.binding && !name) {
1675
1784
  console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1676
1785
  return;
1677
1786
  }
@@ -1915,15 +2024,18 @@ renderMonthField(type, name, label, validate, attributes) {
1915
2024
  }
1916
2025
 
1917
2026
  // Handle the binding syntax
1918
- let bindingDirective = '';
1919
- if (attributes.binding === 'bind:value' && name) {
1920
- bindingDirective = `bind:value="${name}"\n`;
1921
- } if (attributes.binding.startsWith('::') && name) {
1922
- bindingDirective = `bind:value="${name}"\n`;
1923
- } if (attributes.binding && !name) {
1924
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1925
- return;
1926
- }
2027
+ let bindingDirective = '';
2028
+ if (attributes?.binding === 'bind:value' && name) {
2029
+ bindingDirective = `bind:value="${name}"\n`;
2030
+ }
2031
+ if (attributes?.binding?.startsWith('::') && name) {
2032
+ bindingDirective = `bind:value="${name}"\n`;
2033
+ }
2034
+ if (attributes?.binding && !name) {
2035
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2036
+ return;
2037
+ }
2038
+
1927
2039
 
1928
2040
  // Get the id from attributes or fall back to name
1929
2041
  let id = attributes.id || name;
@@ -2037,15 +2149,17 @@ renderWeekField(type, name, label, validate, attributes) {
2037
2149
  }
2038
2150
 
2039
2151
  // Handle the binding syntax
2040
- let bindingDirective = '';
2041
- if (attributes.binding === 'bind:value' && name) {
2042
- bindingDirective = `bind:value="${name}"\n`;
2043
- } if (attributes.binding.startsWith('::') && name) {
2044
- bindingDirective = `bind:value="${name}"\n`;
2045
- } if (attributes.binding && !name) {
2046
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2047
- return;
2048
- }
2152
+ let bindingDirective = '';
2153
+ if (attributes?.binding === 'bind:value' && name) {
2154
+ bindingDirective = `bind:value="${name}"\n`;
2155
+ }
2156
+ if (attributes?.binding?.startsWith('::') && name) {
2157
+ bindingDirective = `bind:value="${name}"\n`;
2158
+ }
2159
+ if (attributes?.binding && !name) {
2160
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2161
+ return;
2162
+ }
2049
2163
 
2050
2164
  // Get the id from attributes or fall back to name
2051
2165
  let id = attributes.id || name;
@@ -2155,15 +2269,18 @@ renderUrlField(type, name, label, validate, attributes) {
2155
2269
  }
2156
2270
 
2157
2271
  // Handle the binding syntax
2158
- let bindingDirective = '';
2159
- if (attributes.binding === 'bind:value' && name) {
2160
- bindingDirective = `bind:value="${name}"\n`;
2161
- } if (attributes.binding.startsWith('::') && name) {
2162
- bindingDirective = `bind:value="${name}"\n`;
2163
- } if (attributes.binding && !name) {
2164
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2165
- return;
2166
- }
2272
+ // Handle the binding syntax
2273
+ let bindingDirective = '';
2274
+ if (attributes?.binding === 'bind:value' && name) {
2275
+ bindingDirective = `bind:value="${name}"\n`;
2276
+ }
2277
+ if (attributes?.binding?.startsWith('::') && name) {
2278
+ bindingDirective = `bind:value="${name}"\n`;
2279
+ }
2280
+ if (attributes?.binding && !name) {
2281
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2282
+ return;
2283
+ }
2167
2284
 
2168
2285
  // Get the id from attributes or fall back to name
2169
2286
  let id = attributes.id || name;
@@ -2272,15 +2389,17 @@ renderSearchField(type, name, label, validate, attributes) {
2272
2389
  }
2273
2390
 
2274
2391
  // Handle the binding syntax
2275
- let bindingDirective = '';
2276
- if (attributes.binding === 'bind:value' && name) {
2277
- bindingDirective = `bind:value="${name}"\n`;
2278
- } if (attributes.binding.startsWith('::') && name) {
2279
- bindingDirective = `bind:value="${name}"\n`;
2280
- } if (attributes.binding && !name) {
2281
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2282
- return;
2283
- }
2392
+ let bindingDirective = '';
2393
+ if (attributes?.binding === 'bind:value' && name) {
2394
+ bindingDirective = `bind:value="${name}"\n`;
2395
+ }
2396
+ if (attributes?.binding?.startsWith('::') && name) {
2397
+ bindingDirective = `bind:value="${name}"\n`;
2398
+ }
2399
+ if (attributes?.binding && !name) {
2400
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2401
+ return;
2402
+ }
2284
2403
 
2285
2404
  // Get the id from attributes or fall back to name
2286
2405
  let id = attributes.id || name;
@@ -2381,14 +2500,16 @@ renderColorField(type, name, label, validate, attributes) {
2381
2500
  });
2382
2501
  }
2383
2502
 
2503
+ // Handle the binding syntax
2384
2504
  // Handle the binding syntax
2385
2505
  let bindingDirective = '';
2386
- if (attributes.binding === 'bind:value') {
2506
+ if (attributes?.binding === 'bind:value' && name) {
2387
2507
  bindingDirective = `bind:value="${name}"\n`;
2388
- } else if (attributes.binding.startsWith('::') && name) {
2508
+ }
2509
+ if (attributes?.binding?.startsWith('::') && name) {
2389
2510
  bindingDirective = `bind:value="${name}"\n`;
2390
2511
  }
2391
- if (attributes.binding && !name) {
2512
+ if (attributes?.binding && !name) {
2392
2513
  console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2393
2514
  return;
2394
2515
  }
@@ -2419,8 +2540,13 @@ renderColorField(type, name, label, validate, attributes) {
2419
2540
  if ('class' in attributes) {
2420
2541
  inputClass = attributes.class;
2421
2542
  } else {
2422
- inputClass = this.inputClass;
2543
+ // inputClass = this.inputClass;
2423
2544
  }
2545
+
2546
+ if (type === 'color') {
2547
+ inputClass += ' form-color-input'; // Add the new specific class for color inputs
2548
+ }
2549
+
2424
2550
  // Construct the final HTML string
2425
2551
  let formHTML = `
2426
2552
  <div class="${this.divClass}" id="${id + '-block'}">
@@ -2493,16 +2619,17 @@ renderFileField(type, name, label, validate, attributes) {
2493
2619
  }
2494
2620
 
2495
2621
  // Handle the binding syntax
2496
- let bindingDirective = '';
2497
- if (attributes.binding === 'bind:value') {
2498
- bindingDirective = `bind:value="${name}"\n`;
2499
- } if (attributes.binding.startsWith('::') && name) {
2500
- bindingDirective = `bind:value="${name}"\n`;
2501
- }
2502
- if (attributes.binding && !name) {
2503
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2504
- return;
2505
- }
2622
+ let bindingDirective = '';
2623
+ if (attributes?.binding === 'bind:value' && name) {
2624
+ bindingDirective = `bind:value="${name}"\n`;
2625
+ }
2626
+ if (attributes?.binding?.startsWith('::') && name) {
2627
+ bindingDirective = `bind:value="${name}"\n`;
2628
+ }
2629
+ if (attributes?.binding && !name) {
2630
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2631
+ return;
2632
+ }
2506
2633
 
2507
2634
  // Get the id from attributes or fall back to name
2508
2635
  let id = attributes.id || name;
@@ -2606,12 +2733,13 @@ renderHiddenField(type, name, label, validate, attributes) {
2606
2733
 
2607
2734
  // Handle the binding syntax
2608
2735
  let bindingDirective = '';
2609
- if (attributes.binding === 'bind:value') {
2736
+ if (attributes?.binding === 'bind:value' && name) {
2610
2737
  bindingDirective = `bind:value="${name}"\n`;
2611
- } if (attributes.binding.startsWith('::') && name) {
2738
+ }
2739
+ if (attributes?.binding?.startsWith('::') && name) {
2612
2740
  bindingDirective = `bind:value="${name}"\n`;
2613
2741
  }
2614
- if (attributes.binding && !name) {
2742
+ if (attributes?.binding && !name) {
2615
2743
  console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2616
2744
  return;
2617
2745
  }
@@ -2648,9 +2776,9 @@ renderHiddenField(type, name, label, validate, attributes) {
2648
2776
  // Construct the final HTML string
2649
2777
  let formHTML = `
2650
2778
  <div class="${this.divClass}" id="${id + '-block'}">
2651
- <label for="${id}">${label}
2779
+ <!--<label for="${id}">${label}
2652
2780
  ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2653
- </label>
2781
+ </label> -->
2654
2782
  <input
2655
2783
  type="${type}"
2656
2784
  name="${name}"
@@ -2683,7 +2811,7 @@ renderHiddenField(type, name, label, validate, attributes) {
2683
2811
  }
2684
2812
 
2685
2813
 
2686
-
2814
+ /*
2687
2815
  renderImageField(type, name, label, validate, attributes) {
2688
2816
  // Define valid validation attributes for image upload
2689
2817
  const imageUploadValidationAttributes = [
@@ -2714,12 +2842,18 @@ renderImageField(type, name, label, validate, attributes) {
2714
2842
  }
2715
2843
 
2716
2844
  // Handle the binding syntax
2717
- let bindingDirective = '';
2718
- if (attributes.binding === 'bind:value') {
2719
- bindingDirective = ` bind:value="${name}"`;
2720
- } else if (attributes.binding.startsWith('::')) {
2721
- bindingDirective = ` bind:value="${name}"`;
2722
- }
2845
+ // Handle the binding syntax
2846
+ let bindingDirective = '';
2847
+ if (attributes?.binding === 'bind:value' && name) {
2848
+ bindingDirective = `bind:value="${name}"\n`;
2849
+ }
2850
+ if (attributes?.binding?.startsWith('::') && name) {
2851
+ bindingDirective = `bind:value="${name}"\n`;
2852
+ }
2853
+ if (attributes?.binding && !name) {
2854
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2855
+ return;
2856
+ }
2723
2857
 
2724
2858
  // Get the id from attributes or fall back to name
2725
2859
  let id = attributes.id || name;
@@ -2786,26 +2920,33 @@ renderImageField(type, name, label, validate, attributes) {
2786
2920
  this.formMarkUp +=formattedHtml;
2787
2921
  }
2788
2922
 
2789
-
2790
-
2923
+ */
2791
2924
 
2792
2925
  renderImageField(type, name, label, validate, attributes) {
2793
- // Define valid validation attributes for image upload
2794
- const imageUploadValidationAttributes = [
2926
+ // Define valid validation attributes for image input
2927
+ const imageValidationAttributes = [
2795
2928
  'accept',
2796
2929
  'required',
2797
2930
  'minwidth',
2798
2931
  'maxwidth',
2799
2932
  'minheight',
2800
2933
  'maxheight',
2934
+ 'src',
2935
+ 'alt',
2936
+ 'width',
2937
+ 'height'
2801
2938
  ];
2802
2939
 
2803
2940
  // Construct validation attributes
2804
2941
  let validationAttrs = '';
2805
2942
  if (validate) {
2806
2943
  Object.entries(validate).forEach(([key, value]) => {
2807
- if (imageUploadValidationAttributes.includes(key)) {
2808
- validationAttrs += `${key}="${value}"\n`;
2944
+ if (imageValidationAttributes.includes(key)) {
2945
+ if (typeof value === 'boolean' && value) {
2946
+ validationAttrs += ` ${key}\n`;
2947
+ } else {
2948
+ validationAttrs += ` ${key}="${value}"\n`;
2949
+ }
2809
2950
  } else {
2810
2951
  console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
2811
2952
  }
@@ -2814,9 +2955,17 @@ renderImageField(type, name, label, validate, attributes) {
2814
2955
 
2815
2956
  // Handle the binding syntax
2816
2957
  let bindingDirective = '';
2817
- if (attributes.binding === 'bind:value' || bindingSyntax.startsWith('::')) {
2958
+ const bindingValue = attributes?.binding;
2959
+ if (bindingValue === 'bind:value' && name) {
2960
+ bindingDirective = `bind:value="${name}"\n`;
2961
+ }
2962
+ if (typeof bindingValue === 'string' && bindingValue.startsWith('::') && name) {
2818
2963
  bindingDirective = `bind:value="${name}"\n`;
2819
2964
  }
2965
+ if (bindingValue && !name) {
2966
+ console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
2967
+ return;
2968
+ }
2820
2969
 
2821
2970
  // Get the id from attributes or fall back to name
2822
2971
  let id = attributes.id || name;
@@ -2824,7 +2973,8 @@ renderImageField(type, name, label, validate, attributes) {
2824
2973
  // Construct additional attributes dynamically
2825
2974
  let additionalAttrs = '';
2826
2975
  for (const [key, value] of Object.entries(attributes)) {
2827
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
2976
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
2977
+ if (key.startsWith('on')) {
2828
2978
  // Handle event attributes
2829
2979
  const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
2830
2980
  additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
@@ -2840,44 +2990,56 @@ renderImageField(type, name, label, validate, attributes) {
2840
2990
  }
2841
2991
  }
2842
2992
 
2843
- let inputClass;
2844
- if ('class' in attributes) {
2845
- inputClass = attributes.class;
2993
+ // Special handling for image submit button
2994
+ let inputElement;
2995
+ if (type === 'image' && name === 'submit') {
2996
+ inputElement = `
2997
+ <input
2998
+ type="image"
2999
+ name="${name}"
3000
+ ${bindingDirective}
3001
+ id="${id}"
3002
+ class="${attributes.class || this.inputClass}"
3003
+ src="${attributes.src || 'img_submit.gif'}"
3004
+ alt="${attributes.alt || 'Submit'}"
3005
+ width="${attributes.width || '48'}"
3006
+ height="${attributes.height || '48'}"
3007
+ ${additionalAttrs}
3008
+ ${validationAttrs}
3009
+ />`;
2846
3010
  } else {
2847
- inputClass = this.inputClass;
2848
- }
2849
- // Construct the final HTML string
2850
- let formHTML = `
2851
- <div class="${this.divClass}" id="${id + '-block'}">
2852
- <label for="${id}">${label}
2853
- ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
2854
- </label>
3011
+ // Regular image input field
3012
+ inputElement = `
2855
3013
  <input
2856
3014
  type="${type}"
2857
3015
  name="${name}"
2858
3016
  ${bindingDirective}
2859
3017
  id="${id}"
2860
- class="${inputClass}"
3018
+ class="${attributes.class || this.inputClass}"
2861
3019
  ${additionalAttrs}
2862
3020
  ${validationAttrs}
2863
- />
3021
+ />`;
3022
+ }
3023
+
3024
+ // Construct the final HTML string
3025
+ let formHTML = `
3026
+ <div class="${this.divClass}" id="${id + '-block'}">
3027
+ ${type === 'image' && name === 'submit' ? '' : `<label for="${id}">${label}
3028
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3029
+ </label>`}
3030
+ ${inputElement}
2864
3031
  </div>
2865
3032
  `.replace(/^\s*\n/gm, '').trim();
2866
3033
 
2867
- let formattedHtml = formHTML;
2868
-
2869
- // Apply vertical layout to the <input> element only
2870
- formattedHtml = formattedHtml.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2871
- // Reformat attributes into a vertical layout
3034
+ // Format the HTML
3035
+ let formattedHtml = formHTML.replace(/<input\s+([^>]*)\/>/, (match, p1) => {
2872
3036
  const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
2873
3037
  return `<input\n${attributes}\n/>`;
2874
3038
  });
2875
3039
 
2876
- // Ensure the <div> block starts on a new line and remove extra blank lines
2877
3040
  formattedHtml = formattedHtml.replace(/(<div\s+[^>]*>)/g, (match) => {
2878
- // Ensure <div> starts on a new line
2879
3041
  return `\n${match}\n`;
2880
- }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3042
+ }).replace(/\n\s*\n/g, '\n');
2881
3043
 
2882
3044
  return formattedHtml;
2883
3045
  }
@@ -2885,8 +3047,6 @@ renderImageField(type, name, label, validate, attributes) {
2885
3047
 
2886
3048
 
2887
3049
 
2888
-
2889
-
2890
3050
  // Textarea field rendering
2891
3051
  renderTextAreaField(type, name, label, validate, attributes) {
2892
3052
  const textAreaValidationAttributes = [
@@ -3239,6 +3399,7 @@ this.renderSingleSelectField(type, name, label, validate, attributes, mainCatego
3239
3399
 
3240
3400
  renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
3241
3401
 
3402
+ console.log("Within");
3242
3403
  // Define valid validation attributes for select fields
3243
3404
  const selectValidationAttributes = ['required'];
3244
3405
 
@@ -3805,10 +3966,11 @@ renderSubmitButton(type, name, label, validate, attributes) {
3805
3966
  }
3806
3967
 
3807
3968
 
3808
- const spinner = `<div class="" id="formiqueSpinner">
3969
+ const spinner = `<div id="formiqueSpinner" style="display: flex; align-items: center; gap: 1rem; font-family: sans-serif; display:none;">
3809
3970
  <div class="formique-spinner"></div>
3810
3971
  <p class="message">Hang in tight, we are submitting your details…</p>
3811
- </div>`;
3972
+ </div>
3973
+ `;
3812
3974
  // Construct the final HTML string
3813
3975
 
3814
3976
  const formHTML = `
@@ -3831,17 +3993,15 @@ const spinner = `<div class="" id="formiqueSpinner">
3831
3993
 
3832
3994
 
3833
3995
 
3834
- renderFormHTML () {
3835
-
3836
- this.formMarkUp+= '</form>';
3837
- //console.log(this.formMarkUp);
3838
- const formContainer = document.getElementById(this.formContainerId);
3839
- //alert(this.formContainerId);
3840
- if (!formContainer) {
3841
- console.error(`Error: formContainer not found. Please ensure an element with id ${this.formContainerId} exists in the HTML.`);
3842
- } else {
3843
- formContainer.innerHTML = this.formMarkUp;
3844
- }
3996
+ renderFormHTML() {
3997
+ this.formMarkUp += '</form>';
3998
+ const formContainer = document.getElementById(this.formContainerId);
3999
+ if (!formContainer) {
4000
+ console.error(`Error: form container with ID ${this.formContainerId} not found. Please ensure an element with id ${this.formContainerId} exists in the HTML.`);
4001
+ } else {
4002
+ formContainer.innerHTML = this.formMarkUp;
4003
+ }
4004
+
3845
4005
 
3846
4006
  //return this.formMarkUp;
3847
4007
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formique/semantq",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Formique is a native form builder for the Semantq JS Framework",
5
5
  "main": "formique-semantq.js",
6
6
  "type": "module",