@formique/semantq 1.0.12 → 1.1.0

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 +705 -100
  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
 
@@ -3455,7 +3503,7 @@ renderTextAreaField(type, name, label, validate, attributes) {
3455
3503
 
3456
3504
  renderRadioField(type, name, label, validate, attributes, options) {
3457
3505
  // Define valid validation attributes for radio fields
3458
- console.log("RADIO DEBUG - options:", JSON.stringify(options, null, 2));
3506
+ //console.log("RADIO DEBUG - options:", JSON.stringify(options, null, 2));
3459
3507
 
3460
3508
  const radioValidationAttributes = ['required'];
3461
3509
 
@@ -3529,10 +3577,10 @@ renderRadioField(type, name, label, validate, attributes, options) {
3529
3577
  // Check options array for selected: true
3530
3578
  if (options && options.length) {
3531
3579
  const selectedOption = options.find(opt => opt.selected === true);
3532
- console.log("RADIO DEBUG - selectedOption:", selectedOption);
3580
+ //console.log("RADIO DEBUG - selectedOption:", selectedOption);
3533
3581
  if (selectedOption) {
3534
3582
  selectedValue = selectedOption.value;
3535
- console.log("RADIO DEBUG - selectedValue:", selectedValue);
3583
+ // console.log("RADIO DEBUG - selectedValue:", selectedValue);
3536
3584
  }
3537
3585
  }
3538
3586
 
@@ -3542,7 +3590,7 @@ renderRadioField(type, name, label, validate, attributes, options) {
3542
3590
  optionsHTML = options.map((option) => {
3543
3591
  // Check if this option should be selected
3544
3592
  const isSelected = (option.value === selectedValue);
3545
- console.log("RADIO DEBUG - option:", option.value, "isSelected:", isSelected);
3593
+ //console.log("RADIO DEBUG - option:", option.value, "isSelected:", isSelected);
3546
3594
  const checkedAttr = isSelected ? ' checked' : '';
3547
3595
 
3548
3596
  return `
@@ -3596,7 +3644,7 @@ renderRadioField(type, name, label, validate, attributes, options) {
3596
3644
 
3597
3645
  renderCheckboxField(type, name, label, validate, attributes, options) {
3598
3646
  // Define valid validation attributes for checkbox fields
3599
- console.log("CHECKBOX DEBUG - options:", JSON.stringify(options, null, 2));
3647
+ //console.log("CHECKBOX DEBUG - options:", JSON.stringify(options, null, 2));
3600
3648
 
3601
3649
  const checkboxValidationAttributes = ['required'];
3602
3650
 
@@ -3663,7 +3711,7 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3663
3711
  }
3664
3712
  });
3665
3713
  }
3666
- console.log("CHECKBOX DEBUG - checkedValues:", checkedValues);
3714
+ //console.log("CHECKBOX DEBUG - checkedValues:", checkedValues);
3667
3715
 
3668
3716
  // Construct checkbox HTML based on options
3669
3717
  let optionsHTML = '';
@@ -3671,7 +3719,7 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3671
3719
  optionsHTML = options.map((option) => {
3672
3720
  const optionId = `${id}-${option.value}`;
3673
3721
  const isChecked = checkedValues.includes(option.value);
3674
- console.log("CHECKBOX DEBUG - option:", option.value, "isChecked:", isChecked);
3722
+ //console.log("CHECKBOX DEBUG - option:", option.value, "isChecked:", isChecked);
3675
3723
  const checkedAttr = isChecked ? ' checked' : '';
3676
3724
 
3677
3725
  return `
@@ -3726,30 +3774,35 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3726
3774
  /* DYNAMIC SINGLE SELECT BLOCK */
3727
3775
 
3728
3776
  // 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
-
3777
+ renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
3778
+ //console.log('DEBUG: renderDynamicSingleSelectField called with options:', options);
3779
+
3780
+ // Step 1: Extract main categories from options
3781
+ const mainCategoryOptions = options.map(item => {
3782
+ // Use item.id as the value for the main select
3783
+ return {
3784
+ value: item.id, // FIXED: Use id, not value
3785
+ label: item.label,
3786
+ // You can add selected logic if needed
3787
+ };
3788
+ });
3789
+
3790
+ // Step 2: The nested options ARE your sub-categories!
3791
+ // Transform the structure
3792
+ const subCategoriesOptions = options.map(item => ({
3793
+ id: item.id, // Same as main category value
3794
+ label: item.label + ' Technologies', // Or customize
3795
+ options: item.options // The nested options array
3796
+ }));
3797
+
3798
+ //console.log('Main categories:', mainCategoryOptions);
3799
+ //console.log('Sub categories:', subCategoriesOptions);
3800
+
3801
+ const mode = 'dynamicSingleSelect';
3802
+
3803
+ // Pass both to the renderer
3804
+ this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
3805
+ }
3753
3806
 
3754
3807
  renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
3755
3808
 
@@ -4356,5 +4409,557 @@ const spinner = `<div id="formiqueSpinner" style="display: flex; align-items: ce
4356
4409
  }
4357
4410
 
4358
4411
 
4412
+ const FORMIQUE_INTERNAL_CSS = `
4413
+ @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600&display=swap');
4414
+
4415
+ /* ==================== */
4416
+ /* BASE FORM VARIABLES */
4417
+ /* ==================== */
4418
+
4419
+ :root {
4420
+ --formique-border-radius: 6px;
4421
+ --formique-padding: 2rem;
4422
+ }
4423
+ /*
4424
+ :root {
4425
+ --formique-base-bg: white;
4426
+ --formique-base-text: #333;
4427
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4428
+ --formique-base-label: #555;
4429
+ --formique-input-border: #ddd;
4430
+ --formique-focus-color: #6a4fbf;
4431
+ --formique-btn-bg: #6a4fbf;
4432
+ --formique-btn-text: white;
4433
+ --formique-btn-shadow: 0 2px 10px rgba(106, 79, 191, 0.3);
4434
+ --formique-border-radius: 6px;
4435
+ --formique-max-width: 100%;
4436
+ --formique-padding: 2rem;
4437
+ }
4438
+ */
4439
+ /* ==================== */
4440
+ /* BASE FORM STYLES */
4441
+ /* ==================== */
4442
+ .formique {
4443
+ width: 100%;
4444
+ max-width: var(--formique-max-width);
4445
+ margin: 2rem auto;
4446
+ padding: var(--formique-padding);
4447
+ background-color: var(--formique-base-bg);
4448
+ border-radius: var(--formique-border-radius);
4449
+ box-shadow: var(--formique-base-shadow);
4450
+ font-family: 'Montserrat', sans-serif;
4451
+ color: var(--formique-base-text);
4452
+ transition: all 0.3s ease;
4453
+ box-sizing: border-box;
4454
+ }
4455
+
4456
+ /* Input Block */
4457
+ .formique .input-block {
4458
+ margin-bottom: 1.5rem;
4459
+ position: relative;
4460
+ }
4461
+
4462
+ .formique .input-block label {
4463
+ display: block;
4464
+ margin-bottom: 0.5rem;
4465
+ font-weight: 500;
4466
+ color: var(--formique-base-label);
4467
+ font-size: 0.9rem;
4468
+ }
4469
+
4470
+ .formique .input-block .form-input,
4471
+ .formique .input-block .form-control {
4472
+ width: 100%;
4473
+ padding: 0.75rem 0;
4474
+ border: none;
4475
+ border-bottom: 1px solid var(--formique-input-border);
4476
+ background-color: transparent;
4477
+ color: var(--formique-base-text);
4478
+ box-sizing: border-box;
4479
+ font-size: 1rem;
4480
+ transition: all 0.3s ease;
4481
+ }
4482
+
4483
+ .formique .input-block .form-input:focus,
4484
+ .formique .input-block .form-control:focus {
4485
+ outline: none;
4486
+ border-bottom-width: 2px;
4487
+ border-bottom-color: var(--formique-focus-color);
4488
+ }
4489
+
4490
+ .formique .input-block .form-input:disabled {
4491
+ opacity: 0.6;
4492
+ cursor: not-allowed;
4493
+ }
4494
+
4495
+ /* Fieldset General Styling */
4496
+ .formique fieldset {
4497
+ border: 1px solid var(--formique-input-border);
4498
+ border-radius: var(--formique-border-radius);
4499
+ padding: 1rem;
4500
+ margin-bottom: 1.5rem;
4501
+ background-color: var(--formique-base-bg);
4502
+ transition: all 0.3s ease;
4503
+ }
4504
+
4505
+ .formique fieldset legend {
4506
+ font-weight: 600;
4507
+ color: var(--formique-base-label);
4508
+ font-size: 1rem;
4509
+ padding: 0 0.5rem;
4510
+ }
4511
+
4512
+ /* Radio Group */
4513
+ .formique .radio-group {
4514
+ /* Styles are now handled by the general fieldset or input-block if used outside fieldset */
4515
+ }
4516
+
4517
+ .formique .radio-group legend {
4518
+ display: block;
4519
+ margin-bottom: 0.75rem;
4520
+ font-weight: 500;
4521
+ color: var(--formique-base-label);
4522
+ font-size: 0.9rem;
4523
+ }
4524
+
4525
+ .formique .radio-group div {
4526
+ margin-bottom: 0.5rem;
4527
+ display: flex;
4528
+ align-items: center;
4529
+ }
4530
+
4531
+ .formique .radio-group .form-radio-input {
4532
+ margin-right: 0.75rem;
4533
+ width: 18px;
4534
+ height: 18px;
4535
+ accent-color: var(--formique-focus-color);
4536
+ cursor: pointer;
4537
+ }
4538
+
4539
+ /* Checkbox Group */
4540
+ .formique .checkbox-group {
4541
+ /* Styles are now handled by the general fieldset or input-block if used outside fieldset */
4542
+ }
4543
+
4544
+ .formique .checkbox-group legend {
4545
+ display: block;
4546
+ margin-bottom: 0.75rem;
4547
+ font-weight: 500;
4548
+ color: var(--formique-base-label);
4549
+ font-size: 0.9rem;
4550
+ }
4551
+
4552
+ .formique .checkbox-group div {
4553
+ margin-bottom: 0.5rem;
4554
+ display: flex;
4555
+ align-items: center;
4556
+ }
4557
+
4558
+ .formique .checkbox-group .form-checkbox-input {
4559
+ margin-right: 0.75rem;
4560
+ width: 18px;
4561
+ height: 18px;
4562
+ accent-color: var(--formique-focus-color);
4563
+ cursor: pointer;
4564
+ }
4565
+
4566
+ /* Select (Dropdowns) */
4567
+ .formique .form-select {
4568
+ margin-bottom: 1.5rem;
4569
+ }
4570
+
4571
+ .formique .form-select label {
4572
+ display: block;
4573
+ margin-bottom: 0.5rem;
4574
+ font-weight: 500;
4575
+ color: var(--formique-base-label);
4576
+ font-size: 0.9rem;
4577
+ }
4578
+
4579
+ .formique .form-select .form-input { /* Changed from .form-select-input to .form-input */
4580
+ width: 100%;
4581
+ padding: 0.75rem;
4582
+ border: 1px solid var(--formique-input-border);
4583
+ border-radius: var(--formique-border-radius);
4584
+ background-color: var(--formique-base-bg);
4585
+ color: var(--formique-base-text);
4586
+ box-sizing: border-box;
4587
+ font-size: 1rem;
4588
+ transition: all 0.3s ease;
4589
+ /* Custom arrow for select element */
4590
+ -webkit-appearance: none;
4591
+ -moz-appearance: none;
4592
+ appearance: none;
4593
+ 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');
4594
+ background-repeat: no-repeat;
4595
+ background-position: right 0.75rem center;
4596
+ background-size: 1rem;
4597
+ cursor: pointer;
4598
+ }
4599
+
4600
+ .formique .form-select .form-input:focus { /* Changed from .form-select-input to .form-input */
4601
+ outline: none;
4602
+ border-color: var(--formique-focus-color);
4603
+ box-shadow: 0 0 0 2px rgba(106, 79, 191, 0.1);
4604
+ }
4605
+
4606
+ /* Multiple Selects */
4607
+ .formique .form-select .form-input[multiple] {
4608
+ min-height: 100px; /* Adjust as needed */
4609
+ padding: 0.5rem;
4610
+ background-image: none; /* Remove custom arrow for multiselect */
4611
+ }
4612
+
4613
+ /* Submit Button */
4614
+ .formique .form-submit-btn {
4615
+ display: block;
4616
+ width: 100%;
4617
+ padding: 0.875rem 1.75rem;
4618
+ border: none;
4619
+ border-radius: var(--formique-border-radius);
4620
+ background-color: var(--formique-btn-bg);
4621
+ color: var(--formique-btn-text);
4622
+ font-size: 1rem;
4623
+ font-weight: 500;
4624
+ cursor: pointer;
4625
+ transition: all 0.3s ease;
4626
+ box-shadow: var(--formique-btn-shadow);
4627
+ box-sizing: border-box;
4628
+ }
4629
+
4630
+ .formique .form-submit-btn:hover {
4631
+ transform: translateY(-1px);
4632
+ box-shadow: 0 4px 15px var(--formique-btn-shadow);
4633
+ }
4634
+
4635
+ .formique .form-submit-btn:active {
4636
+ transform: translateY(0);
4637
+ }
4638
+
4639
+ /* ==================== */
4640
+ /* THEME DEFINITIONS */
4641
+ /* ==================== */
4642
+ .dark-theme {
4643
+ --formique-base-bg: #1e1e1e;
4644
+ --formique-base-text: #e0e0e0;
4645
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
4646
+ --formique-base-label: #b0b0b0;
4647
+ --formique-input-border: #444;
4648
+ --formique-focus-color: #b0b0b0;
4649
+ --formique-btn-bg: #b0b0b0;
4650
+ --formique-btn-text: #1e1e1e;
4651
+ --formique-btn-shadow: 0 2px 10px rgba(176, 176, 176, 0.3);
4652
+ }
4653
+
4654
+ .light-theme {
4655
+ --formique-base-bg: #ffffff;
4656
+ --formique-base-text: #333333;
4657
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4658
+ --formique-base-label: #555555;
4659
+ --formique-input-border: #dddddd;
4660
+ --formique-focus-color: #555555;
4661
+ --formique-btn-bg: #777777;
4662
+ --formique-btn-text: #ffffff;
4663
+ --formique-btn-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
4664
+ }
4665
+
4666
+ .pink-theme {
4667
+ --formique-base-bg: #ffffff;
4668
+ --formique-base-text: #333333;
4669
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4670
+ --formique-base-label: #555555;
4671
+ --formique-input-border: #dddddd;
4672
+ --formique-focus-color: #ff4081;
4673
+ --formique-btn-bg: #ff4081;
4674
+ --formique-btn-text: #ffffff;
4675
+ --formique-btn-shadow: 0 2px 10px rgba(255, 64, 129, 0.3);
4676
+ }
4677
+
4678
+ .indigo-theme {
4679
+ --formique-base-bg: #ffffff;
4680
+ --formique-base-text: #333333;
4681
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4682
+ --formique-base-label: #555555;
4683
+ --formique-input-border: #dddddd;
4684
+ --formique-focus-color: #3f51b5;
4685
+ --formique-btn-bg: #3f51b5;
4686
+ --formique-btn-text: #ffffff;
4687
+ --formique-btn-shadow: 0 2px 10px rgba(63, 81, 181, 0.3);
4688
+ }
4689
+
4690
+ .dark-blue-theme {
4691
+ --formique-base-bg: #0a192f;
4692
+ --formique-base-text: #e6f1ff;
4693
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
4694
+ --formique-base-label: #a8b2d1;
4695
+ --formique-input-border: #233554;
4696
+ --formique-focus-color: #64ffda;
4697
+ --formique-btn-bg: #64ffda;
4698
+ --formique-btn-text: #0a192f;
4699
+ --formique-btn-shadow: 0 2px 10px rgba(100, 255, 218, 0.3);
4700
+ }
4701
+
4702
+ .light-blue-theme {
4703
+ --formique-base-bg: #f5f9ff;
4704
+ --formique-base-text: #2a4365;
4705
+ --formique-base-shadow: 0 10px 30px rgba(66, 153, 225, 0.1);
4706
+ --formique-base-label: #4299e1;
4707
+ --formique-input-border: #bee3f8;
4708
+ --formique-focus-color: #3182ce;
4709
+ --formique-btn-bg: #3182ce;
4710
+ --formique-btn-text: #ffffff;
4711
+ --formique-btn-shadow: 0 2px 10px rgba(49, 130, 206, 0.3);
4712
+ }
4713
+
4714
+ .dark-orange-theme {
4715
+ --formique-base-bg: #2d3748;
4716
+ --formique-base-text: #f7fafc;
4717
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
4718
+ --formique-base-label: #cbd5e0;
4719
+ --formique-input-border: #4a5568;
4720
+ --formique-focus-color: #ed8936;
4721
+ --formique-btn-bg: #ed8936;
4722
+ --formique-btn-text: #1a202c;
4723
+ --formique-btn-shadow: 0 2px 10px rgba(237, 137, 54, 0.3);
4724
+ }
4725
+
4726
+ .bright-yellow-theme {
4727
+ --formique-base-bg: #ffffff;
4728
+ --formique-base-text: #1a202c;
4729
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4730
+ --formique-base-label: #4a5568;
4731
+ --formique-input-border: #e2e8f0;
4732
+ --formique-focus-color: #f6e05e;
4733
+ --formique-btn-bg: #f6e05e;
4734
+ --formique-btn-text: #1a202c;
4735
+ --formique-btn-shadow: 0 2px 10px rgba(246, 224, 94, 0.3);
4736
+ }
4737
+
4738
+ .green-theme {
4739
+ --formique-base-bg: #ffffff;
4740
+ --formique-base-text: #1a202c;
4741
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4742
+ --formique-base-label: #4a5568;
4743
+ --formique-input-border: #e2e8f0;
4744
+ --formique-focus-color: #48bb78;
4745
+ --formique-btn-bg: #48bb78;
4746
+ --formique-btn-text: #ffffff;
4747
+ --formique-btn-shadow: 0 2px 10px rgba(72, 187, 120, 0.3);
4748
+ }
4749
+
4750
+ .purple-theme {
4751
+ --formique-base-bg: #ffffff;
4752
+ --formique-base-text: #1a202c;
4753
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4754
+ --formique-base-label: #4a5568;
4755
+ --formique-input-border: #e2e8f0;
4756
+ --formique-focus-color: #9f7aea;
4757
+ --formique-btn-bg: #9f7aea;
4758
+ --formique-btn-text: #ffffff;
4759
+ --formique-btn-shadow: 0 2px 10px rgba(159, 122, 234, 0.3);
4760
+ }
4761
+
4762
+ .midnight-blush-theme {
4763
+ --formique-base-bg: #1a1a2e;
4764
+ --formique-base-text: #e6e6e6;
4765
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
4766
+ --formique-base-label: #b8b8b8;
4767
+ --formique-input-border: #4e4e6a;
4768
+ --formique-focus-color: #f67280;
4769
+ --formique-btn-bg: #f67280;
4770
+ --formique-btn-text: #1a1a2e;
4771
+ --formique-btn-shadow: 0 2px 10px rgba(246, 114, 128, 0.3);
4772
+ }
4773
+
4774
+ .deep-blue-theme {
4775
+ --formique-base-bg: #0f172a;
4776
+ --formique-base-text: #e2e8f0;
4777
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
4778
+ --formique-base-label: #94a3b8;
4779
+ --formique-input-border: #1e293b;
4780
+ --formique-focus-color: #60a5fa;
4781
+ --formique-btn-bg: #60a5fa;
4782
+ --formique-btn-text: #0f172a;
4783
+ --formique-btn-shadow: 0 2px 10px rgba(96, 165, 250, 0.3);
4784
+ }
4785
+
4786
+ .blue-theme {
4787
+ --formique-base-bg: #ffffff;
4788
+ --formique-base-text: #1e3a8a;
4789
+ --formique-base-shadow: 0 10px 30px rgba(29, 78, 216, 0.1);
4790
+ --formique-base-label: #3b82f6;
4791
+ --formique-input-border: #bfdbfe;
4792
+ --formique-focus-color: #2563eb;
4793
+ --formique-btn-bg: #2563eb;
4794
+ --formique-btn-text: #ffffff;
4795
+ --formique-btn-shadow: 0 2px 10px rgba(37, 99, 235, 0.3);
4796
+ }
4797
+
4798
+ .brown-theme {
4799
+ --formique-base-bg: #f5f5f5;
4800
+ --formique-base-text: #3e2723;
4801
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4802
+ --formique-base-label: #5d4037;
4803
+ --formique-input-border: #d7ccc8;
4804
+ --formique-focus-color: #8d6e63;
4805
+ --formique-btn-bg: #6d4c41;
4806
+ --formique-btn-text: #ffffff;
4807
+ --formique-btn-shadow: 0 2px 10px rgba(109, 76, 65, 0.3);
4808
+ }
4809
+
4810
+ .orange-theme {
4811
+ --formique-base-bg: #ffffff;
4812
+ --formique-base-text: #7b341e;
4813
+ --formique-base-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
4814
+ --formique-base-label: #dd6b20;
4815
+ --formique-input-border: #fed7aa;
4816
+ --formique-focus-color: #ed8936;
4817
+ --formique-btn-bg: #ed8936;
4818
+ --formique-btn-text: #ffffff;
4819
+ --formique-btn-shadow: 0 2px 10px rgba(237, 137, 54, 0.3);
4820
+ }
4821
+ /* ==================== */
4822
+ /* WIDTH CONTROL CLASSES */
4823
+ /* ==================== */
4824
+ .formique {
4825
+ padding: 1rem;
4826
+ }
4827
+ .formique.width-full {
4828
+ --formique-max-width: 100%;
4829
+ }
4830
+
4831
+ .formique.width-half {
4832
+ --formique-max-width: 50%;
4833
+ }
4834
+
4835
+ .formique.width-medium {
4836
+ --formique-max-width: 600px;
4837
+ }
4838
+
4839
+ .formique.width-small {
4840
+ --formique-max-width: 400px;
4841
+ }
4842
+
4843
+ .formique.width-custom {
4844
+ /* To be set inline or via JS */
4845
+ }
4846
+
4847
+ /* Spinner Container */
4848
+ #formiqueSpinner {
4849
+ display: none;
4850
+ align-items: center;
4851
+ gap: 1rem;
4852
+ font-family: var(--formique-font-family, 'Montserrat, sans-serif');
4853
+ padding: 1rem;
4854
+ border-radius: var(--formique-border-radius, 6px);
4855
+ background-color: var(--formique-base-bg);
4856
+ color: var(--formique-base-text);
4857
+ margin-top: 1rem;
4858
+ }
4859
+
4860
+ /* Spinner Circle */
4861
+ .formique-spinner {
4862
+ width: 1.5rem;
4863
+ height: 1.5rem;
4864
+ border: 3px solid rgba(0, 0, 0, 0.1);
4865
+ border-radius: 50%;
4866
+ border-top-color: var(--formique-btn-bg);
4867
+ animation: formique-spin 1s ease-in-out infinite;
4868
+ }
4869
+
4870
+ /* Spinner Animation */
4871
+ @keyframes formique-spin {
4872
+ to { transform: rotate(360deg); }
4873
+ }
4874
+
4875
+ /* Message */
4876
+ #formiqueSpinner .message {
4877
+ margin: 0;
4878
+ font-size: 0.9rem;
4879
+ color: var(--formique-focus-color);
4880
+ }
4881
+
4882
+
4883
+ .formique-success, .formique-error {
4884
+ /* Background with opacity to work with both themes */
4885
+ background-color: var(--formique-base-bg); /* Based on --formique-btn-bg */
4886
+
4887
+ /* Text styling using theme variables */
4888
+ color: var(--formique-focus-color);
4889
+ font-family: inherit;
4890
+ font-size: 0.95rem;
4891
+
4892
+ /* Border using focus color with opacity */
4893
+ border: 1px solid var(--formique-focus-color); /* Based on --formique-btn-bg */
4894
+ border-radius: 4px;
4895
+ padding: 12px 16px;
4896
+ margin: 16px 0;
4897
+
4898
+ /* Layout */
4899
+ display: flex;
4900
+ align-items: center;
4901
+ gap: 8px;
4902
+
4903
+ /* Animation */
4904
+ animation: fadeIn 0.3s ease-in-out;
4905
+
4906
+ /* Shadow using theme variable */
4907
+ box-shadow: var(--formique-base-shadow);
4908
+ }
4909
+
4910
+ .formique-success::before {
4911
+ content: "✓";
4912
+ color: var(--formique-btn-bg); /* Using button background color for checkmark */
4913
+ font-weight: bold;
4914
+ font-size: 1.2rem;
4915
+ }
4916
+
4917
+ .formique-error::before {
4918
+ content: "✗";
4919
+ color: var(--formique-btn-bg); /* Using button background color for checkmark */
4920
+ font-weight: bold;
4921
+ font-size: 1.2rem;
4922
+ }
4923
+
4924
+ @keyframes fadeIn {
4925
+ from { opacity: 0; transform: translateY(-10px); }
4926
+ to { opacity: 1; transform: translateY(0); }
4927
+ }
4928
+
4929
+
4930
+ /* --- */
4931
+ /* Specific Styles for Color Input */
4932
+ .formique .input-block .form-color-input {
4933
+ /* Restore native appearance */
4934
+ -webkit-appearance: auto;
4935
+ -moz-appearance: auto;
4936
+ appearance: auto;
4937
+
4938
+ /* Reset properties that typically interfere with native color inputs */
4939
+ padding: 2px; /* Small padding to allow native swatch to show */
4940
+ border: 1px solid var(--formique-input-border); /* Visible border */
4941
+ background-color: var(--formique-base-bg); /* Ensure it has a background */
4942
+ width: 50px; /* A typical width for the color swatch */
4943
+ height: 30px; /* A typical height for the color swatch */
4944
+ cursor: pointer; /* Indicates it's interactive */
4945
+
4946
+ /* Ensure border-radius and vertical alignment blend with other inputs */
4947
+ border-radius: var(--formique-border-radius);
4948
+ vertical-align: middle; /* Aligns with text if label is inline */
4949
+
4950
+ /* Override any focus border-bottom rules from general .form-input if needed */
4951
+ border-bottom: 1px solid var(--formique-input-border); /* Keep consistent border for focus */
4952
+ }
4953
+
4954
+ .formique .input-block .form-color-input:focus {
4955
+ outline: none; /* Remove default browser outline */
4956
+ border-color: var(--formique-focus-color); /* Apply theme focus color */
4957
+ border-bottom-color: var(--formique-focus-color); /* Ensure bottom border matches on focus */
4958
+ box-shadow: 0 0 0 2px rgba(var(--formique-focus-color-rgb, 106, 79, 191), 0.1); /* Optional: subtle shadow */
4959
+ }
4960
+
4961
+ `;
4962
+
4963
+
4359
4964
 
4360
- export default Formique;
4965
+ 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.0",
4
4
  "description": "Formique is a native form builder for the Semantq JS Framework",
5
5
  "main": "formique-semantq.js",
6
6
  "type": "module",