@formique/semantq 1.0.12 → 1.1.1

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 (2) hide show
  1. package/formique-semantq.js +750 -278
  2. package/package.json +1 -1
@@ -54,6 +54,7 @@ class FormBuilder
54
54
  class Formique extends FormBuilder {
55
55
  constructor(formDefinition, formSettings = {}, formParams = {}) {
56
56
  super();
57
+
57
58
  let formSchema;
58
59
  let finalSettings = formSettings;
59
60
  let finalParams = formParams;
@@ -83,6 +84,11 @@ class Formique extends FormBuilder {
83
84
  ...finalSettings
84
85
  };
85
86
 
87
+ // Only inject CSS if the user hasn't explicitly disabled it
88
+ if (this.formSettings.disableStyles !== true) {
89
+ this.injectInternalStyles();
90
+ }
91
+
86
92
  //console.log("constructor",this.formSettings);
87
93
 
88
94
  this.themeColor = this.formSettings.themeColor || null;
@@ -225,14 +231,49 @@ this.initDependencyGraph();
225
231
  this.registerObservers();
226
232
  this.attachDynamicSelectListeners();
227
233
 
228
- // Apply theme
229
- if (this.themeColor) {
230
- this.applyCustomTheme(this.themeColor, this.formContainerId);
231
- } else if (this.formSettings.theme && this.themes.includes(this.formSettings.theme)) {
232
- let theme = this.formSettings.theme;
233
- this.applyTheme(theme, this.formContainerId);
234
+
235
+ // In your constructor, after attachDynamicSelectListeners:
236
+ //console.log('FORM SCHEMA FOR DYNAMIC SELECT:');
237
+ const dynamicField = this.formSchema.find(f => f[0] === 'dynamicSingleSelect');
238
+ if (dynamicField) {
239
+ //console.log('Main options:', dynamicField[5]);
240
+ //console.log('Sub options:', dynamicField[6]);
241
+
242
+ // Check the IDs
243
+ const mainOptions = dynamicField[5] || [];
244
+ mainOptions.forEach(opt => {
245
+ //console.log(`Option ID: ${opt.id}, Label: ${opt.label}`);
246
+ });
247
+ }
248
+
249
+
250
+ // --- CONDITIONAL CONSOLIDATED THEME LOGIC ---
251
+ if (this.formSettings.disableStyles !== true) {
252
+ const container = document.getElementById(this.formContainerId);
253
+
254
+ if (container) {
255
+ // Apply the base class regardless of theme choice
256
+ container.classList.add('formique');
257
+
258
+ if (this.themeColor) {
259
+ // Priority 1: Custom Hex Color Override
260
+ this.applyCustomTheme(this.themeColor, this.formContainerId);
261
+ } else {
262
+ // Priority 2: Named Theme (or fallback to 'dark')
263
+ const activeTheme = (this.formSettings.theme && this.themes.includes(this.formSettings.theme))
264
+ ? this.formSettings.theme
265
+ : 'light';
266
+ this.applyTheme(activeTheme, this.formContainerId);
267
+ }
268
+
269
+ // Priority 3: Manual Inline Style Overrides (Highest Specificity)
270
+ if (this.formSettings.formContainerStyle) {
271
+ container.style.cssText += this.formSettings.formContainerStyle;
272
+ }
273
+ }
234
274
  } else {
235
- this.applyTheme('dark', this.formContainerId); // Default to 'dark'
275
+ // Optional: Log that styles are being skipped for easier debugging
276
+ console.log("Formique: Internal styles disabled by user settings.");
236
277
  }
237
278
 
238
279
 
@@ -244,6 +285,35 @@ if (this.themeColor) {
244
285
  }
245
286
 
246
287
 
288
+
289
+
290
+
291
+
292
+ injectInternalStyles() {
293
+ if (document.getElementById('formique-internal-css')) return;
294
+
295
+ const style = document.createElement('style');
296
+ style.id = 'formique-internal-css';
297
+ style.textContent = FORMIQUE_INTERNAL_CSS;
298
+ document.head.appendChild(style);
299
+ }
300
+
301
+
302
+
303
+ applyCustomTheme(color, formContainerId) {
304
+ const container = document.getElementById(formContainerId);
305
+ if (!container) return;
306
+
307
+ // Set variables directly on the element (highest specificity)
308
+ container.style.setProperty('--formique-focus-color', color);
309
+ container.style.setProperty('--formique-btn-bg', color);
310
+
311
+ // Add a transparent shadow using the hex color
312
+ container.style.setProperty('--formique-btn-shadow', `0 4px 14px ${color}66`);
313
+ }
314
+
315
+
316
+
247
317
  generateFormId() {
248
318
  return `fmq-${Math.random().toString(36).substr(2, 10)}`;
249
319
  }
@@ -450,48 +520,59 @@ registerObservers() {
450
520
 
451
521
  // --- NEW METHOD FOR DYNAMIC SELECT LISTENERS ---
452
522
  attachDynamicSelectListeners() {
523
+ //console.log('DEBUG: attachDynamicSelectListeners called');
524
+
453
525
  this.formSchema.forEach(field => {
454
526
  const [type, name, label, validate, attributes = {}] = field;
455
527
 
456
528
  if (type === 'dynamicSingleSelect') {
457
529
  const mainSelectId = attributes.id || name;
530
+ //console.log('Setting up dynamic select for:', mainSelectId);
531
+
458
532
  const mainSelectElement = document.getElementById(mainSelectId);
459
533
 
460
534
  if (mainSelectElement) {
535
+ //console.log('Found main select element:', mainSelectElement);
536
+
461
537
  mainSelectElement.addEventListener('change', (event) => {
462
- const selectedCategory = event.target.value; // e.g., 'frontend', 'backend', 'server'
463
-
538
+ const selectedCategory = event.target.value;
539
+ //console.log('Select changed to:', selectedCategory);
540
+
464
541
  // Find all sub-category fieldsets related to this main select
465
542
  const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}`);
466
-
543
+ //console.log(`Found ${subCategoryFieldsets.length} fieldsets with class .${mainSelectId}`);
544
+
467
545
  subCategoryFieldsets.forEach(fieldset => {
468
- const subSelect = fieldset.querySelector('select'); // Get the actual select element
546
+ //console.log('Hiding fieldset:', fieldset.id);
547
+ const subSelect = fieldset.querySelector('select');
469
548
  if (subSelect) {
470
- // Save original required state (if it was true) then set to false if hidden
471
549
  subSelect.setAttribute('data-original-required', subSelect.required.toString());
472
- subSelect.required = false; // Always set to false when hiding
550
+ subSelect.required = false;
473
551
  }
474
- fieldset.style.display = 'none'; // Hide all sub-category fieldsets initially
552
+ fieldset.style.display = 'none';
475
553
  });
476
554
 
477
- // Show the selected sub-category fieldset and manage its required state
478
- const selectedFieldsetId = selectedCategory; // Matches the ID format in renderSingleSelectField
555
+ // Show the selected sub-category fieldset
556
+ const selectedFieldsetId = selectedCategory;
557
+ //console.log('Looking for fieldset with ID:', selectedFieldsetId);
558
+
479
559
  const selectedFieldset = document.getElementById(selectedFieldsetId);
480
-
560
+
481
561
  if (selectedFieldset) {
482
- selectedFieldset.style.display = 'block'; // Show the selected one
562
+ // console.log('Found selected fieldset, showing it:', selectedFieldset);
563
+ selectedFieldset.style.display = 'block';
483
564
  const selectedSubSelect = selectedFieldset.querySelector('select');
484
565
  if (selectedSubSelect) {
485
- // Restore original required state for the visible select
486
566
  selectedSubSelect.required = selectedSubSelect.getAttribute('data-original-required') === 'true';
487
567
  }
568
+ } else {
569
+ console.warn('Selected fieldset not found with ID:', selectedFieldsetId);
488
570
  }
489
571
  });
490
572
 
491
- // IMPORTANT: Trigger the change listener once on load if a default option is selected
492
- // This ensures correct initial visibility and required states if there's a pre-selected main category.
493
- // We do this by dispatching a 'change' event programmatically if the select has a value.
573
+ // Trigger initial state
494
574
  if (mainSelectElement.value) {
575
+ console.log('Triggering initial change event for:', mainSelectId);
495
576
  const event = new Event('change');
496
577
  mainSelectElement.dispatchEvent(event);
497
578
  }
@@ -502,61 +583,28 @@ attachDynamicSelectListeners() {
502
583
  });
503
584
  }
504
585
 
505
- applyTheme(theme, formContainerId) {
506
- //const stylesheet = document.querySelector('link[formique-style]');
507
-
508
- const stylesheet = document.querySelector('link[href*="formique-css"]');
509
-
510
- if (!stylesheet) {
511
- console.error("Stylesheet with 'formique-css' in the name not found!");
512
- return;
513
- }
514
586
 
515
- fetch(stylesheet.href)
516
- .then(response => response.text())
517
- .then(cssText => {
518
- // Extract theme-specific CSS rules
519
- const themeRules = cssText.match(new RegExp(`\\.${theme}-theme\\s*{([^}]*)}`, 'i'));
520
587
 
521
- if (!themeRules) {
522
- console.error(`Theme rules for ${theme} not found in the stylesheet.`);
588
+ applyTheme(theme, formContainerId) {
589
+ // Safety Guard: If styles are disabled, exit immediately
590
+ if (this.formSettings.disableStyles === true) {
591
+ // console.warn('applyTheme ignored because disableStyles is true.');
523
592
  return;
524
- }
525
-
526
- // Extract CSS rules for the theme
527
- const themeCSS = themeRules[1].trim();
528
-
529
- // Find the form container element
530
- const formContainer = document.getElementById(formContainerId);
593
+ }
531
594
 
532
- if (formContainer) {
533
- // Append the theme class to the form container
595
+ const formContainer = document.getElementById(formContainerId);
596
+ if (formContainer) {
597
+ // Apply classes for backward compatibility
534
598
  formContainer.classList.add(`${theme}-theme`, 'formique');
535
599
 
536
-
537
- // Create a <style> tag with the extracted theme styles
538
- const clonedStyle = document.createElement('style');
539
- clonedStyle.textContent = `
540
- #${formContainerId} {
541
- ${themeCSS}
542
- }
543
- `;
544
-
545
- // Insert the <style> tag above the form container
546
- formContainer.parentNode.insertBefore(clonedStyle, formContainer);
547
-
548
- // console.log(`Applied ${theme} theme to form container: ${formContainerId}`);
549
- } else {
600
+ // Set the data attribute for our internal CSS variables
601
+ formContainer.setAttribute('data-theme', theme);
602
+ } else {
550
603
  console.error(`Form container with ID ${formContainerId} not found.`);
551
- }
552
- })
553
- .catch(error => {
554
- console.error('Error loading the stylesheet:', error);
555
- });
604
+ }
556
605
  }
557
606
 
558
607
 
559
-
560
608
  // New method to apply a custom theme based on a color
561
609
  applyCustomTheme(color, formContainerId) {
562
610
  const formContainer = document.getElementById(formContainerId);
@@ -612,7 +660,7 @@ applyCustomTheme(color, formContainerId) {
612
660
  // Insert the style element into the head or before the form container
613
661
  formContainer.parentNode.insertBefore(styleElement, formContainer);
614
662
 
615
- console.log(`Applied custom theme with color: ${color} to form container: ${formContainerId}`);
663
+ //console.log(`Applied custom theme with color: ${color} to form container: ${formContainerId}`);
616
664
  }
617
665
 
618
666
 
@@ -1680,12 +1728,10 @@ if (attributes.binding === 'bind:value' && name) {
1680
1728
  // Textarea field rendering
1681
1729
 
1682
1730
  renderTextAreaField(type, name, label, validate, attributes) {
1683
- const textInputValidationAttributes = [
1684
- 'required',
1685
- 'minlength',
1686
- 'maxlength',
1687
- 'pattern',
1688
- ];
1731
+ const textInputValidationAttributes = ['required', 'minlength', 'maxlength', 'pattern'];
1732
+
1733
+ // 1. Extract content value to place between tags later
1734
+ const textareaValue = attributes.value || '';
1689
1735
 
1690
1736
  // Construct validation attributes
1691
1737
  let validationAttrs = '';
@@ -1695,115 +1741,74 @@ renderTextAreaField(type, name, label, validate, attributes) {
1695
1741
  if (typeof value === 'boolean' && value) {
1696
1742
  validationAttrs += ` ${key}\n`;
1697
1743
  } else {
1698
- switch (key) {
1699
- case 'pattern':
1700
- case 'minlength':
1701
- case 'maxlength':
1702
- validationAttrs += ` ${key}="${value}"\n`;
1703
- break;
1704
- default:
1705
- if (!textInputValidationAttributes.includes(key)) {
1706
- console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
1707
- }
1708
- break;
1709
- }
1744
+ validationAttrs += ` ${key}="${value}"\n`;
1710
1745
  }
1711
1746
  } else {
1712
- console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'text'.\x1b[0m`);
1747
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'textarea'.\x1b[0m`);
1713
1748
  }
1714
1749
  });
1715
1750
  }
1716
1751
 
1717
-
1718
-
1719
1752
  // Handle the binding syntax
1720
1753
  let bindingDirective = '';
1721
- if (attributes.binding) {
1722
- if (attributes.binding === 'bind:value' && name) {
1754
+ if (attributes.binding && name) {
1723
1755
  bindingDirective = `bind:value="${name}"\n`;
1724
1756
  }
1725
- if (attributes.binding.startsWith('::') && name) {
1726
- bindingDirective = `bind:value="${name}"\n`;
1727
- }
1728
- if (attributes.binding && !name) {
1729
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1730
- return;
1731
- }
1732
- }
1733
1757
 
1734
-
1735
-
1736
- // Get the id from attributes or fall back to name
1737
1758
  let id = attributes.id || name;
1738
- // Determine if semanti is true based on formSettings
1739
1759
  const framework = this.formSettings?.framework || false;
1740
1760
 
1741
- // Construct additional attributes dynamically
1761
+ // Construct additional attributes (excluding internal keys and the 'value' content)
1742
1762
  let additionalAttrs = '';
1743
1763
  for (const [key, value] of Object.entries(attributes)) {
1744
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1745
- // Handle event attributes
1746
- if (framework === 'semantq') {
1747
- const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1748
- additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1749
- } else {
1750
- // Add parentheses if not present
1751
- const eventValue = value.endsWith('()') ? value : `${value}()`;
1752
- additionalAttrs += ` ${key}="${eventValue}"\n`;
1753
- }
1764
+ if (!['id', 'class', 'dependsOn', 'dependents', 'value', 'binding'].includes(key) && value !== undefined) {
1765
+ if (key.startsWith('on')) {
1766
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
1767
+ additionalAttrs += ` ${key}="${eventValue}"\n`;
1754
1768
  } else {
1755
- // Handle boolean attributes
1769
+ const attrName = key.replace(/_/g, '-');
1756
1770
  if (value === true) {
1757
- additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1771
+ additionalAttrs += ` ${attrName}\n`;
1758
1772
  } else if (value !== false) {
1759
- // Convert underscores to hyphens and set the attribute
1760
- additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1773
+ additionalAttrs += ` ${attrName}="${value}"\n`;
1761
1774
  }
1762
1775
  }
1763
1776
  }
1764
1777
  }
1765
1778
 
1779
+ let inputClass = attributes.class || this.inputClass;
1766
1780
 
1781
+ // Build the raw HTML structure
1782
+ let formHTML = `
1783
+ <div class="${this.divClass}" id="${id + '-block'}">
1784
+ <label for="${id}">${label}
1785
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1786
+ </label>
1787
+ <textarea
1788
+ name="${name}"
1789
+ ${bindingDirective}
1790
+ id="${id}"
1791
+ class="${inputClass}"
1792
+ ${additionalAttrs}
1793
+ ${validationAttrs}
1794
+ ${(additionalAttrs.includes('placeholder') || attributes.placeholder) ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>${textareaValue}</textarea>
1795
+ </div>`.replace(/^\s*\n/gm, '').trim();
1796
+
1797
+ // Vertical layout formatting for attributes while preserving content
1798
+ let formattedHtml = formHTML.replace(/<textarea\s+([\s\S]*?)>([\s\S]*?)<\/textarea>/, (match, attrPart, content) => {
1799
+ // Split by whitespace only if it is NOT inside quotes (prevents stacking text/placeholders)
1800
+ const attrs = attrPart.trim()
1801
+ .split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/)
1802
+ .filter(a => a.trim() !== '')
1803
+ .map(attr => ` ${attr.trim()}`)
1804
+ .join('\n');
1805
+
1806
+ return `<textarea\n${attrs}\n>${content}</textarea>`;
1807
+ });
1767
1808
 
1768
- let inputClass;
1769
- if ('class' in attributes) {
1770
- inputClass = attributes.class;
1771
- } else {
1772
- inputClass = this.inputClass;
1773
- }
1774
-
1775
- // Construct the final HTML string for textarea
1776
- let formHTML = `
1777
- <div class="${this.divClass}" id="${id + '-block'}">
1778
- <label for="${id}">${label}
1779
- ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1780
- </label>
1781
- <textarea
1782
- name="${name}"
1783
- ${bindingDirective}
1784
- id="${id}"
1785
- class="${inputClass}"
1786
- ${additionalAttrs}
1787
- ${validationAttrs}
1788
- ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
1789
- </textarea>
1790
- </div>
1791
- `.replace(/^\s*\n/gm, '').trim();
1792
-
1793
- let formattedHtml = formHTML;
1794
-
1795
- // Apply vertical layout to the <textarea> element only
1796
- formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
1797
- // Reformat attributes into a vertical layout
1798
- const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1799
- return `<textarea\n${attributes}\n></textarea>`;
1800
- });
1801
-
1802
- this.formMarkUp += formattedHtml;
1803
-
1809
+ this.formMarkUp += formattedHtml;
1804
1810
  }
1805
1811
 
1806
-
1807
1812
  // New method for rendering tel fields
1808
1813
  renderTelField(type, name, label, validate, attributes) {
1809
1814
 
@@ -3361,101 +3366,11 @@ renderImageField(type, name, label, validate, attributes) {
3361
3366
 
3362
3367
 
3363
3368
 
3364
- // Textarea field rendering
3365
- renderTextAreaField(type, name, label, validate, attributes) {
3366
- const textAreaValidationAttributes = [
3367
- 'required',
3368
- 'minlength',
3369
- 'maxlength'
3370
- ];
3371
-
3372
- // Construct validation attributes
3373
- let validationAttrs = '';
3374
- if (validate) {
3375
- Object.entries(validate).forEach(([key, value]) => {
3376
- if (textAreaValidationAttributes.includes(key)) {
3377
- if (typeof value === 'boolean' && value) {
3378
- validationAttrs += ` ${key}\n`;
3379
- } else {
3380
- validationAttrs += ` ${key}="${value}"\n`;
3381
- }
3382
- } else {
3383
- console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
3384
- }
3385
- });
3386
- }
3387
-
3388
- // Handle the binding syntax
3389
- let bindingDirective = '';
3390
- if (attributes.binding) {
3391
- if (attributes.binding === 'bind:value' && name) {
3392
- bindingDirective = `bind:value="${name}"\n`;
3393
- }
3394
- if (attributes.binding.startsWith('::') && name) {
3395
- bindingDirective = `bind:value="${name}"\n`;
3396
- }
3397
- if (attributes.binding && !name) {
3398
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
3399
- return;
3400
- }
3401
- }
3402
-
3403
- // Get the id from attributes or fall back to name
3404
- let id = attributes.id || name;
3405
-
3406
- // Construct additional attributes dynamically
3407
- let additionalAttrs = '';
3408
- for (const [key, value] of Object.entries(attributes)) {
3409
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
3410
- if (key.startsWith('on')) {
3411
- const eventValue = value.endsWith('()') ? value : `${value}()`;
3412
- additionalAttrs += ` ${key}="${eventValue}"\n`;
3413
- } else {
3414
- if (value === true) {
3415
- additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3416
- } else if (value !== false) {
3417
- additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3418
- }
3419
- }
3420
- }
3421
- }
3422
-
3423
- let inputClass = attributes.class || this.inputClass;
3424
-
3425
- // Construct the final HTML string
3426
- let formHTML = `
3427
- <div class="${this.divClass}" id="${id + '-block'}">
3428
- <label for="${id}">${label}
3429
- ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3430
- </label>
3431
- <textarea
3432
- name="${name}"
3433
- ${bindingDirective}
3434
- id="${id}"
3435
- class="${inputClass}"
3436
- ${additionalAttrs}
3437
- ${validationAttrs}
3438
- ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
3439
- </textarea>
3440
- </div>
3441
- `.replace(/^\s*\n/gm, '').trim();
3442
-
3443
- let formattedHtml = formHTML;
3444
-
3445
- // Apply vertical layout to the <textarea> element only
3446
- formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
3447
- const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3448
- return `<textarea\n${attributes}\n></textarea>`;
3449
- });
3450
-
3451
- this.formMarkUp += formattedHtml;
3452
- }
3453
-
3454
-
3369
+ // Radio field rendering
3455
3370
 
3456
3371
  renderRadioField(type, name, label, validate, attributes, options) {
3457
3372
  // Define valid validation attributes for radio fields
3458
- console.log("RADIO DEBUG - options:", JSON.stringify(options, null, 2));
3373
+ //console.log("RADIO DEBUG - options:", JSON.stringify(options, null, 2));
3459
3374
 
3460
3375
  const radioValidationAttributes = ['required'];
3461
3376
 
@@ -3529,10 +3444,10 @@ renderRadioField(type, name, label, validate, attributes, options) {
3529
3444
  // Check options array for selected: true
3530
3445
  if (options && options.length) {
3531
3446
  const selectedOption = options.find(opt => opt.selected === true);
3532
- console.log("RADIO DEBUG - selectedOption:", selectedOption);
3447
+ //console.log("RADIO DEBUG - selectedOption:", selectedOption);
3533
3448
  if (selectedOption) {
3534
3449
  selectedValue = selectedOption.value;
3535
- console.log("RADIO DEBUG - selectedValue:", selectedValue);
3450
+ // console.log("RADIO DEBUG - selectedValue:", selectedValue);
3536
3451
  }
3537
3452
  }
3538
3453
 
@@ -3542,7 +3457,7 @@ renderRadioField(type, name, label, validate, attributes, options) {
3542
3457
  optionsHTML = options.map((option) => {
3543
3458
  // Check if this option should be selected
3544
3459
  const isSelected = (option.value === selectedValue);
3545
- console.log("RADIO DEBUG - option:", option.value, "isSelected:", isSelected);
3460
+ //console.log("RADIO DEBUG - option:", option.value, "isSelected:", isSelected);
3546
3461
  const checkedAttr = isSelected ? ' checked' : '';
3547
3462
 
3548
3463
  return `
@@ -3596,7 +3511,7 @@ renderRadioField(type, name, label, validate, attributes, options) {
3596
3511
 
3597
3512
  renderCheckboxField(type, name, label, validate, attributes, options) {
3598
3513
  // Define valid validation attributes for checkbox fields
3599
- console.log("CHECKBOX DEBUG - options:", JSON.stringify(options, null, 2));
3514
+ //console.log("CHECKBOX DEBUG - options:", JSON.stringify(options, null, 2));
3600
3515
 
3601
3516
  const checkboxValidationAttributes = ['required'];
3602
3517
 
@@ -3663,7 +3578,7 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3663
3578
  }
3664
3579
  });
3665
3580
  }
3666
- console.log("CHECKBOX DEBUG - checkedValues:", checkedValues);
3581
+ //console.log("CHECKBOX DEBUG - checkedValues:", checkedValues);
3667
3582
 
3668
3583
  // Construct checkbox HTML based on options
3669
3584
  let optionsHTML = '';
@@ -3671,7 +3586,7 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3671
3586
  optionsHTML = options.map((option) => {
3672
3587
  const optionId = `${id}-${option.value}`;
3673
3588
  const isChecked = checkedValues.includes(option.value);
3674
- console.log("CHECKBOX DEBUG - option:", option.value, "isChecked:", isChecked);
3589
+ //console.log("CHECKBOX DEBUG - option:", option.value, "isChecked:", isChecked);
3675
3590
  const checkedAttr = isChecked ? ' checked' : '';
3676
3591
 
3677
3592
  return `
@@ -3726,30 +3641,35 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3726
3641
  /* DYNAMIC SINGLE SELECT BLOCK */
3727
3642
 
3728
3643
  // Function to render the dynamic select field and update based on user selection
3729
- renderDynamicSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions) {
3730
-
3731
- // Step 1: Transform the data into an array of objects
3732
- const mainCategoryOptions = options.map(item => {
3733
- // CRITICAL GUARD FIX: Check for item.options existence to prevent crash
3734
- const selected = item.options
3735
- ? item.options.some(option => option.selected === true)
3736
- : item.selected === true;
3737
-
3738
- // Create a transformed object
3739
- return {
3740
- value: item.value,
3741
- label: item.label,
3742
- ...(selected && { selected: true })
3743
- };
3744
- });
3745
-
3746
- const mode = 'dynamicSingleSelect';
3747
-
3748
- // Pass the main options and the nested sub categories options to the single select renderer
3749
- this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
3750
- }
3751
-
3752
-
3644
+ renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
3645
+ //console.log('DEBUG: renderDynamicSingleSelectField called with options:', options);
3646
+
3647
+ // Step 1: Extract main categories from options
3648
+ const mainCategoryOptions = options.map(item => {
3649
+ // Use item.id as the value for the main select
3650
+ return {
3651
+ value: item.id, // FIXED: Use id, not value
3652
+ label: item.label,
3653
+ // You can add selected logic if needed
3654
+ };
3655
+ });
3656
+
3657
+ // Step 2: The nested options ARE your sub-categories!
3658
+ // Transform the structure
3659
+ const subCategoriesOptions = options.map(item => ({
3660
+ id: item.id, // Same as main category value
3661
+ label: item.label + ' Technologies', // Or customize
3662
+ options: item.options // The nested options array
3663
+ }));
3664
+
3665
+ //console.log('Main categories:', mainCategoryOptions);
3666
+ //console.log('Sub categories:', subCategoriesOptions);
3667
+
3668
+ const mode = 'dynamicSingleSelect';
3669
+
3670
+ // Pass both to the renderer
3671
+ this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
3672
+ }
3753
3673
 
3754
3674
  renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
3755
3675
 
@@ -4356,5 +4276,557 @@ const spinner = `<div id="formiqueSpinner" style="display: flex; align-items: ce
4356
4276
  }
4357
4277
 
4358
4278
 
4279
+ const FORMIQUE_INTERNAL_CSS = `
4280
+ @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600&display=swap');
4281
+
4282
+ /* ==================== */
4283
+ /* BASE FORM VARIABLES */
4284
+ /* ==================== */
4285
+
4286
+ :root {
4287
+ --formique-border-radius: 6px;
4288
+ --formique-padding: 2rem;
4289
+ }
4290
+ /*
4291
+ :root {
4292
+ --formique-base-bg: white;
4293
+ --formique-base-text: #333;
4294
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4295
+ --formique-base-label: #555;
4296
+ --formique-input-border: #ddd;
4297
+ --formique-focus-color: #6a4fbf;
4298
+ --formique-btn-bg: #6a4fbf;
4299
+ --formique-btn-text: white;
4300
+ --formique-btn-shadow: 0 2px 10px rgba(106, 79, 191, 0.3);
4301
+ --formique-border-radius: 6px;
4302
+ --formique-max-width: 100%;
4303
+ --formique-padding: 2rem;
4304
+ }
4305
+ */
4306
+ /* ==================== */
4307
+ /* BASE FORM STYLES */
4308
+ /* ==================== */
4309
+ .formique {
4310
+ width: 100%;
4311
+ max-width: var(--formique-max-width);
4312
+ margin: 2rem auto;
4313
+ padding: var(--formique-padding);
4314
+ background-color: var(--formique-base-bg);
4315
+ border-radius: var(--formique-border-radius);
4316
+ box-shadow: var(--formique-base-shadow);
4317
+ font-family: 'Montserrat', sans-serif;
4318
+ color: var(--formique-base-text);
4319
+ transition: all 0.3s ease;
4320
+ box-sizing: border-box;
4321
+ }
4322
+
4323
+ /* Input Block */
4324
+ .formique .input-block {
4325
+ margin-bottom: 1.5rem;
4326
+ position: relative;
4327
+ }
4328
+
4329
+ .formique .input-block label {
4330
+ display: block;
4331
+ margin-bottom: 0.5rem;
4332
+ font-weight: 500;
4333
+ color: var(--formique-base-label);
4334
+ font-size: 0.9rem;
4335
+ }
4336
+
4337
+ .formique .input-block .form-input,
4338
+ .formique .input-block .form-control {
4339
+ width: 100%;
4340
+ padding: 0.75rem 0;
4341
+ border: none;
4342
+ border-bottom: 1px solid var(--formique-input-border);
4343
+ background-color: transparent;
4344
+ color: var(--formique-base-text);
4345
+ box-sizing: border-box;
4346
+ font-size: 1rem;
4347
+ transition: all 0.3s ease;
4348
+ }
4349
+
4350
+ .formique .input-block .form-input:focus,
4351
+ .formique .input-block .form-control:focus {
4352
+ outline: none;
4353
+ border-bottom-width: 2px;
4354
+ border-bottom-color: var(--formique-focus-color);
4355
+ }
4356
+
4357
+ .formique .input-block .form-input:disabled {
4358
+ opacity: 0.6;
4359
+ cursor: not-allowed;
4360
+ }
4361
+
4362
+ /* Fieldset General Styling */
4363
+ .formique fieldset {
4364
+ border: 1px solid var(--formique-input-border);
4365
+ border-radius: var(--formique-border-radius);
4366
+ padding: 1rem;
4367
+ margin-bottom: 1.5rem;
4368
+ background-color: var(--formique-base-bg);
4369
+ transition: all 0.3s ease;
4370
+ }
4371
+
4372
+ .formique fieldset legend {
4373
+ font-weight: 600;
4374
+ color: var(--formique-base-label);
4375
+ font-size: 1rem;
4376
+ padding: 0 0.5rem;
4377
+ }
4378
+
4379
+ /* Radio Group */
4380
+ .formique .radio-group {
4381
+ /* Styles are now handled by the general fieldset or input-block if used outside fieldset */
4382
+ }
4383
+
4384
+ .formique .radio-group legend {
4385
+ display: block;
4386
+ margin-bottom: 0.75rem;
4387
+ font-weight: 500;
4388
+ color: var(--formique-base-label);
4389
+ font-size: 0.9rem;
4390
+ }
4391
+
4392
+ .formique .radio-group div {
4393
+ margin-bottom: 0.5rem;
4394
+ display: flex;
4395
+ align-items: center;
4396
+ }
4397
+
4398
+ .formique .radio-group .form-radio-input {
4399
+ margin-right: 0.75rem;
4400
+ width: 18px;
4401
+ height: 18px;
4402
+ accent-color: var(--formique-focus-color);
4403
+ cursor: pointer;
4404
+ }
4405
+
4406
+ /* Checkbox Group */
4407
+ .formique .checkbox-group {
4408
+ /* Styles are now handled by the general fieldset or input-block if used outside fieldset */
4409
+ }
4410
+
4411
+ .formique .checkbox-group legend {
4412
+ display: block;
4413
+ margin-bottom: 0.75rem;
4414
+ font-weight: 500;
4415
+ color: var(--formique-base-label);
4416
+ font-size: 0.9rem;
4417
+ }
4418
+
4419
+ .formique .checkbox-group div {
4420
+ margin-bottom: 0.5rem;
4421
+ display: flex;
4422
+ align-items: center;
4423
+ }
4424
+
4425
+ .formique .checkbox-group .form-checkbox-input {
4426
+ margin-right: 0.75rem;
4427
+ width: 18px;
4428
+ height: 18px;
4429
+ accent-color: var(--formique-focus-color);
4430
+ cursor: pointer;
4431
+ }
4432
+
4433
+ /* Select (Dropdowns) */
4434
+ .formique .form-select {
4435
+ margin-bottom: 1.5rem;
4436
+ }
4437
+
4438
+ .formique .form-select label {
4439
+ display: block;
4440
+ margin-bottom: 0.5rem;
4441
+ font-weight: 500;
4442
+ color: var(--formique-base-label);
4443
+ font-size: 0.9rem;
4444
+ }
4445
+
4446
+ .formique .form-select .form-input { /* Changed from .form-select-input to .form-input */
4447
+ width: 100%;
4448
+ padding: 0.75rem;
4449
+ border: 1px solid var(--formique-input-border);
4450
+ border-radius: var(--formique-border-radius);
4451
+ background-color: var(--formique-base-bg);
4452
+ color: var(--formique-base-text);
4453
+ box-sizing: border-box;
4454
+ font-size: 1rem;
4455
+ transition: all 0.3s ease;
4456
+ /* Custom arrow for select element */
4457
+ -webkit-appearance: none;
4458
+ -moz-appearance: none;
4459
+ appearance: none;
4460
+ background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20fill%3D%22%23'+encodeURIComponent(var(--formique-base-text)).substring(1)+'%22%20d%3D%22M208.5%2084.5l-80%2080a12%2012%200%2001-17%200l-80-80a12%2012%200%200117-17L128%20139l71.5-71.5a12%2012%200%200117%2017z%22%2F%3E%3C%2Fsvg%3E');
4461
+ background-repeat: no-repeat;
4462
+ background-position: right 0.75rem center;
4463
+ background-size: 1rem;
4464
+ cursor: pointer;
4465
+ }
4466
+
4467
+ .formique .form-select .form-input:focus { /* Changed from .form-select-input to .form-input */
4468
+ outline: none;
4469
+ border-color: var(--formique-focus-color);
4470
+ box-shadow: 0 0 0 2px rgba(106, 79, 191, 0.1);
4471
+ }
4472
+
4473
+ /* Multiple Selects */
4474
+ .formique .form-select .form-input[multiple] {
4475
+ min-height: 100px; /* Adjust as needed */
4476
+ padding: 0.5rem;
4477
+ background-image: none; /* Remove custom arrow for multiselect */
4478
+ }
4479
+
4480
+ /* Submit Button */
4481
+ .formique .form-submit-btn {
4482
+ display: block;
4483
+ width: 100%;
4484
+ padding: 0.875rem 1.75rem;
4485
+ border: none;
4486
+ border-radius: var(--formique-border-radius);
4487
+ background-color: var(--formique-btn-bg);
4488
+ color: var(--formique-btn-text);
4489
+ font-size: 1rem;
4490
+ font-weight: 500;
4491
+ cursor: pointer;
4492
+ transition: all 0.3s ease;
4493
+ box-shadow: var(--formique-btn-shadow);
4494
+ box-sizing: border-box;
4495
+ }
4496
+
4497
+ .formique .form-submit-btn:hover {
4498
+ transform: translateY(-1px);
4499
+ box-shadow: 0 4px 15px var(--formique-btn-shadow);
4500
+ }
4501
+
4502
+ .formique .form-submit-btn:active {
4503
+ transform: translateY(0);
4504
+ }
4505
+
4506
+ /* ==================== */
4507
+ /* THEME DEFINITIONS */
4508
+ /* ==================== */
4509
+ .dark-theme {
4510
+ --formique-base-bg: #1e1e1e;
4511
+ --formique-base-text: #e0e0e0;
4512
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
4513
+ --formique-base-label: #b0b0b0;
4514
+ --formique-input-border: #444;
4515
+ --formique-focus-color: #b0b0b0;
4516
+ --formique-btn-bg: #b0b0b0;
4517
+ --formique-btn-text: #1e1e1e;
4518
+ --formique-btn-shadow: 0 2px 10px rgba(176, 176, 176, 0.3);
4519
+ }
4520
+
4521
+ .light-theme {
4522
+ --formique-base-bg: #ffffff;
4523
+ --formique-base-text: #333333;
4524
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4525
+ --formique-base-label: #555555;
4526
+ --formique-input-border: #dddddd;
4527
+ --formique-focus-color: #555555;
4528
+ --formique-btn-bg: #777777;
4529
+ --formique-btn-text: #ffffff;
4530
+ --formique-btn-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
4531
+ }
4532
+
4533
+ .pink-theme {
4534
+ --formique-base-bg: #ffffff;
4535
+ --formique-base-text: #333333;
4536
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4537
+ --formique-base-label: #555555;
4538
+ --formique-input-border: #dddddd;
4539
+ --formique-focus-color: #ff4081;
4540
+ --formique-btn-bg: #ff4081;
4541
+ --formique-btn-text: #ffffff;
4542
+ --formique-btn-shadow: 0 2px 10px rgba(255, 64, 129, 0.3);
4543
+ }
4544
+
4545
+ .indigo-theme {
4546
+ --formique-base-bg: #ffffff;
4547
+ --formique-base-text: #333333;
4548
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4549
+ --formique-base-label: #555555;
4550
+ --formique-input-border: #dddddd;
4551
+ --formique-focus-color: #3f51b5;
4552
+ --formique-btn-bg: #3f51b5;
4553
+ --formique-btn-text: #ffffff;
4554
+ --formique-btn-shadow: 0 2px 10px rgba(63, 81, 181, 0.3);
4555
+ }
4556
+
4557
+ .dark-blue-theme {
4558
+ --formique-base-bg: #0a192f;
4559
+ --formique-base-text: #e6f1ff;
4560
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
4561
+ --formique-base-label: #a8b2d1;
4562
+ --formique-input-border: #233554;
4563
+ --formique-focus-color: #64ffda;
4564
+ --formique-btn-bg: #64ffda;
4565
+ --formique-btn-text: #0a192f;
4566
+ --formique-btn-shadow: 0 2px 10px rgba(100, 255, 218, 0.3);
4567
+ }
4568
+
4569
+ .light-blue-theme {
4570
+ --formique-base-bg: #f5f9ff;
4571
+ --formique-base-text: #2a4365;
4572
+ --formique-base-shadow: 0 10px 30px rgba(66, 153, 225, 0.1);
4573
+ --formique-base-label: #4299e1;
4574
+ --formique-input-border: #bee3f8;
4575
+ --formique-focus-color: #3182ce;
4576
+ --formique-btn-bg: #3182ce;
4577
+ --formique-btn-text: #ffffff;
4578
+ --formique-btn-shadow: 0 2px 10px rgba(49, 130, 206, 0.3);
4579
+ }
4580
+
4581
+ .dark-orange-theme {
4582
+ --formique-base-bg: #2d3748;
4583
+ --formique-base-text: #f7fafc;
4584
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
4585
+ --formique-base-label: #cbd5e0;
4586
+ --formique-input-border: #4a5568;
4587
+ --formique-focus-color: #ed8936;
4588
+ --formique-btn-bg: #ed8936;
4589
+ --formique-btn-text: #1a202c;
4590
+ --formique-btn-shadow: 0 2px 10px rgba(237, 137, 54, 0.3);
4591
+ }
4592
+
4593
+ .bright-yellow-theme {
4594
+ --formique-base-bg: #ffffff;
4595
+ --formique-base-text: #1a202c;
4596
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4597
+ --formique-base-label: #4a5568;
4598
+ --formique-input-border: #e2e8f0;
4599
+ --formique-focus-color: #f6e05e;
4600
+ --formique-btn-bg: #f6e05e;
4601
+ --formique-btn-text: #1a202c;
4602
+ --formique-btn-shadow: 0 2px 10px rgba(246, 224, 94, 0.3);
4603
+ }
4604
+
4605
+ .green-theme {
4606
+ --formique-base-bg: #ffffff;
4607
+ --formique-base-text: #1a202c;
4608
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4609
+ --formique-base-label: #4a5568;
4610
+ --formique-input-border: #e2e8f0;
4611
+ --formique-focus-color: #48bb78;
4612
+ --formique-btn-bg: #48bb78;
4613
+ --formique-btn-text: #ffffff;
4614
+ --formique-btn-shadow: 0 2px 10px rgba(72, 187, 120, 0.3);
4615
+ }
4616
+
4617
+ .purple-theme {
4618
+ --formique-base-bg: #ffffff;
4619
+ --formique-base-text: #1a202c;
4620
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4621
+ --formique-base-label: #4a5568;
4622
+ --formique-input-border: #e2e8f0;
4623
+ --formique-focus-color: #9f7aea;
4624
+ --formique-btn-bg: #9f7aea;
4625
+ --formique-btn-text: #ffffff;
4626
+ --formique-btn-shadow: 0 2px 10px rgba(159, 122, 234, 0.3);
4627
+ }
4628
+
4629
+ .midnight-blush-theme {
4630
+ --formique-base-bg: #1a1a2e;
4631
+ --formique-base-text: #e6e6e6;
4632
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
4633
+ --formique-base-label: #b8b8b8;
4634
+ --formique-input-border: #4e4e6a;
4635
+ --formique-focus-color: #f67280;
4636
+ --formique-btn-bg: #f67280;
4637
+ --formique-btn-text: #1a1a2e;
4638
+ --formique-btn-shadow: 0 2px 10px rgba(246, 114, 128, 0.3);
4639
+ }
4640
+
4641
+ .deep-blue-theme {
4642
+ --formique-base-bg: #0f172a;
4643
+ --formique-base-text: #e2e8f0;
4644
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
4645
+ --formique-base-label: #94a3b8;
4646
+ --formique-input-border: #1e293b;
4647
+ --formique-focus-color: #60a5fa;
4648
+ --formique-btn-bg: #60a5fa;
4649
+ --formique-btn-text: #0f172a;
4650
+ --formique-btn-shadow: 0 2px 10px rgba(96, 165, 250, 0.3);
4651
+ }
4652
+
4653
+ .blue-theme {
4654
+ --formique-base-bg: #ffffff;
4655
+ --formique-base-text: #1e3a8a;
4656
+ --formique-base-shadow: 0 10px 30px rgba(29, 78, 216, 0.1);
4657
+ --formique-base-label: #3b82f6;
4658
+ --formique-input-border: #bfdbfe;
4659
+ --formique-focus-color: #2563eb;
4660
+ --formique-btn-bg: #2563eb;
4661
+ --formique-btn-text: #ffffff;
4662
+ --formique-btn-shadow: 0 2px 10px rgba(37, 99, 235, 0.3);
4663
+ }
4664
+
4665
+ .brown-theme {
4666
+ --formique-base-bg: #f5f5f5;
4667
+ --formique-base-text: #3e2723;
4668
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4669
+ --formique-base-label: #5d4037;
4670
+ --formique-input-border: #d7ccc8;
4671
+ --formique-focus-color: #8d6e63;
4672
+ --formique-btn-bg: #6d4c41;
4673
+ --formique-btn-text: #ffffff;
4674
+ --formique-btn-shadow: 0 2px 10px rgba(109, 76, 65, 0.3);
4675
+ }
4676
+
4677
+ .orange-theme {
4678
+ --formique-base-bg: #ffffff;
4679
+ --formique-base-text: #7b341e;
4680
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4681
+ --formique-base-label: #dd6b20;
4682
+ --formique-input-border: #fed7aa;
4683
+ --formique-focus-color: #ed8936;
4684
+ --formique-btn-bg: #ed8936;
4685
+ --formique-btn-text: #ffffff;
4686
+ --formique-btn-shadow: 0 2px 10px rgba(237, 137, 54, 0.3);
4687
+ }
4688
+ /* ==================== */
4689
+ /* WIDTH CONTROL CLASSES */
4690
+ /* ==================== */
4691
+ .formique {
4692
+ padding: 1rem;
4693
+ }
4694
+ .formique.width-full {
4695
+ --formique-max-width: 100%;
4696
+ }
4697
+
4698
+ .formique.width-half {
4699
+ --formique-max-width: 50%;
4700
+ }
4701
+
4702
+ .formique.width-medium {
4703
+ --formique-max-width: 600px;
4704
+ }
4705
+
4706
+ .formique.width-small {
4707
+ --formique-max-width: 400px;
4708
+ }
4709
+
4710
+ .formique.width-custom {
4711
+ /* To be set inline or via JS */
4712
+ }
4713
+
4714
+ /* Spinner Container */
4715
+ #formiqueSpinner {
4716
+ display: none;
4717
+ align-items: center;
4718
+ gap: 1rem;
4719
+ font-family: var(--formique-font-family, 'Montserrat, sans-serif');
4720
+ padding: 1rem;
4721
+ border-radius: var(--formique-border-radius, 6px);
4722
+ background-color: var(--formique-base-bg);
4723
+ color: var(--formique-base-text);
4724
+ margin-top: 1rem;
4725
+ }
4726
+
4727
+ /* Spinner Circle */
4728
+ .formique-spinner {
4729
+ width: 1.5rem;
4730
+ height: 1.5rem;
4731
+ border: 3px solid rgba(0, 0, 0, 0.1);
4732
+ border-radius: 50%;
4733
+ border-top-color: var(--formique-btn-bg);
4734
+ animation: formique-spin 1s ease-in-out infinite;
4735
+ }
4736
+
4737
+ /* Spinner Animation */
4738
+ @keyframes formique-spin {
4739
+ to { transform: rotate(360deg); }
4740
+ }
4741
+
4742
+ /* Message */
4743
+ #formiqueSpinner .message {
4744
+ margin: 0;
4745
+ font-size: 0.9rem;
4746
+ color: var(--formique-focus-color);
4747
+ }
4748
+
4749
+
4750
+ .formique-success, .formique-error {
4751
+ /* Background with opacity to work with both themes */
4752
+ background-color: var(--formique-base-bg); /* Based on --formique-btn-bg */
4753
+
4754
+ /* Text styling using theme variables */
4755
+ color: var(--formique-focus-color);
4756
+ font-family: inherit;
4757
+ font-size: 0.95rem;
4758
+
4759
+ /* Border using focus color with opacity */
4760
+ border: 1px solid var(--formique-focus-color); /* Based on --formique-btn-bg */
4761
+ border-radius: 4px;
4762
+ padding: 12px 16px;
4763
+ margin: 16px 0;
4764
+
4765
+ /* Layout */
4766
+ display: flex;
4767
+ align-items: center;
4768
+ gap: 8px;
4769
+
4770
+ /* Animation */
4771
+ animation: fadeIn 0.3s ease-in-out;
4772
+
4773
+ /* Shadow using theme variable */
4774
+ box-shadow: var(--formique-base-shadow);
4775
+ }
4776
+
4777
+ .formique-success::before {
4778
+ content: "✓";
4779
+ color: var(--formique-btn-bg); /* Using button background color for checkmark */
4780
+ font-weight: bold;
4781
+ font-size: 1.2rem;
4782
+ }
4783
+
4784
+ .formique-error::before {
4785
+ content: "✗";
4786
+ color: var(--formique-btn-bg); /* Using button background color for checkmark */
4787
+ font-weight: bold;
4788
+ font-size: 1.2rem;
4789
+ }
4790
+
4791
+ @keyframes fadeIn {
4792
+ from { opacity: 0; transform: translateY(-10px); }
4793
+ to { opacity: 1; transform: translateY(0); }
4794
+ }
4795
+
4796
+
4797
+ /* --- */
4798
+ /* Specific Styles for Color Input */
4799
+ .formique .input-block .form-color-input {
4800
+ /* Restore native appearance */
4801
+ -webkit-appearance: auto;
4802
+ -moz-appearance: auto;
4803
+ appearance: auto;
4804
+
4805
+ /* Reset properties that typically interfere with native color inputs */
4806
+ padding: 2px; /* Small padding to allow native swatch to show */
4807
+ border: 1px solid var(--formique-input-border); /* Visible border */
4808
+ background-color: var(--formique-base-bg); /* Ensure it has a background */
4809
+ width: 50px; /* A typical width for the color swatch */
4810
+ height: 30px; /* A typical height for the color swatch */
4811
+ cursor: pointer; /* Indicates it's interactive */
4812
+
4813
+ /* Ensure border-radius and vertical alignment blend with other inputs */
4814
+ border-radius: var(--formique-border-radius);
4815
+ vertical-align: middle; /* Aligns with text if label is inline */
4816
+
4817
+ /* Override any focus border-bottom rules from general .form-input if needed */
4818
+ border-bottom: 1px solid var(--formique-input-border); /* Keep consistent border for focus */
4819
+ }
4820
+
4821
+ .formique .input-block .form-color-input:focus {
4822
+ outline: none; /* Remove default browser outline */
4823
+ border-color: var(--formique-focus-color); /* Apply theme focus color */
4824
+ border-bottom-color: var(--formique-focus-color); /* Ensure bottom border matches on focus */
4825
+ box-shadow: 0 0 0 2px rgba(var(--formique-focus-color-rgb, 106, 79, 191), 0.1); /* Optional: subtle shadow */
4826
+ }
4827
+
4828
+ `;
4829
+
4830
+
4359
4831
 
4360
- export default Formique;
4832
+ export default Formique;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formique/semantq",
3
- "version": "1.0.12",
3
+ "version": "1.1.1",
4
4
  "description": "Formique is a native form builder for the Semantq JS Framework",
5
5
  "main": "formique-semantq.js",
6
6
  "type": "module",