@formique/semantq 1.1.0 → 1.1.2

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.
@@ -782,51 +782,47 @@ renderForm() {
782
782
 
783
783
  // renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
784
784
  renderField(type, name, label, validate, attributes, options, subOptions = undefined) {
785
- const fieldRenderMap = {
786
- 'text': this.renderTextField,
787
- 'email': this.renderEmailField,
788
- 'number': this.renderNumberField,
789
- 'password': this.renderPasswordField,
790
- 'textarea': this.renderTextAreaField,
791
- 'tel': this.renderTelField,
792
- 'date': this.renderDateField,
793
- 'time': this.renderTimeField,
794
- 'datetime-local': this.renderDateTimeField,
795
- 'month': this.renderMonthField,
796
- 'week': this.renderWeekField,
797
- 'url': this.renderUrlField,
798
- 'search': this.renderSearchField,
799
- 'color': this.renderColorField,
800
- 'checkbox': this.renderCheckboxField,
801
- 'radio': this.renderRadioField,
802
- 'file': this.renderFileField,
803
- 'hidden': this.renderHiddenField,
804
- 'image': this.renderImageField,
805
- 'singleSelect': this.renderSingleSelectField,
806
- 'multipleSelect': this.renderMultipleSelectField,
807
- 'dynamicSingleSelect': this.renderDynamicSingleSelectField,
808
- 'range': this.renderRangeField,
809
- 'recaptcha': this.renderRecaptchaField,
810
- 'submit': this.renderSubmitButton, // Keep this for completeness, but renderSubmitButtonElement will now handle it
811
- };
812
-
813
- const renderMethod = fieldRenderMap[type];
785
+ const fieldRenderMap = {
786
+ 'text': this.renderTextField,
787
+ 'email': this.renderEmailField,
788
+ 'number': this.renderNumberField,
789
+ 'password': this.renderPasswordField,
790
+ 'textarea': this.renderTextAreaField,
791
+ 'tel': this.renderTelField,
792
+ 'date': this.renderDateField,
793
+ 'time': this.renderTimeField,
794
+ 'datetime-local': this.renderDateTimeField,
795
+ 'month': this.renderMonthField,
796
+ 'week': this.renderWeekField,
797
+ 'url': this.renderUrlField,
798
+ 'search': this.renderSearchField,
799
+ 'color': this.renderColorField,
800
+ 'checkbox': this.renderCheckboxField,
801
+ 'radio': this.renderRadioField,
802
+ 'file': this.renderFileField,
803
+ 'hidden': this.renderHiddenField,
804
+ 'image': this.renderImageField,
805
+ 'singleSelect': this.renderSingleSelectField,
806
+ 'multipleSelect': this.renderMultipleSelectField,
807
+ 'dynamicSingleSelect': this.renderDynamicSingleSelectField,
808
+ 'range': this.renderRangeField,
809
+ 'recaptcha': this.renderRecaptchaField,
810
+ 'submit': this.renderSubmitButton,
811
+ };
814
812
 
815
- if (renderMethod) {
816
- // If the type is 'submit', ensure we use the specific renderSubmitButtonElement
817
- // Although, with the filter in renderForm(), this branch for 'submit' type
818
- // might not be hit in the primary rendering flow, it's good practice.
819
- return renderMethod.call(this, type, name, label, validate, attributes, options,subOptions);
813
+ const renderMethod = fieldRenderMap[type];
820
814
 
821
- if (type === 'submit') {
822
- return this.renderSubmitButton(type, name, label, validate, attributes, options);
823
- }
824
- //return renderMethod.call(this, type, name, label, validate, attributes, options);
825
- } else {
826
- console.warn(`Unsupported field type '${type}' encountered.`);
827
- return '';
828
- }
815
+ if (renderMethod) {
816
+ // IMPORTANT: Pass ALL arguments including subOptions
817
+ return renderMethod.call(this, type, name, label, validate, attributes, options, subOptions);
818
+ } else {
819
+ console.warn(`Unsupported field type '${type}' encountered.`);
820
+ return '';
829
821
  }
822
+ }
823
+
824
+
825
+
830
826
 
831
827
 
832
828
 
@@ -1728,12 +1724,10 @@ if (attributes.binding === 'bind:value' && name) {
1728
1724
  // Textarea field rendering
1729
1725
 
1730
1726
  renderTextAreaField(type, name, label, validate, attributes) {
1731
- const textInputValidationAttributes = [
1732
- 'required',
1733
- 'minlength',
1734
- 'maxlength',
1735
- 'pattern',
1736
- ];
1727
+ const textInputValidationAttributes = ['required', 'minlength', 'maxlength', 'pattern'];
1728
+
1729
+ // 1. Extract content value to place between tags later
1730
+ const textareaValue = attributes.value || '';
1737
1731
 
1738
1732
  // Construct validation attributes
1739
1733
  let validationAttrs = '';
@@ -1743,115 +1737,74 @@ renderTextAreaField(type, name, label, validate, attributes) {
1743
1737
  if (typeof value === 'boolean' && value) {
1744
1738
  validationAttrs += ` ${key}\n`;
1745
1739
  } else {
1746
- switch (key) {
1747
- case 'pattern':
1748
- case 'minlength':
1749
- case 'maxlength':
1750
- validationAttrs += ` ${key}="${value}"\n`;
1751
- break;
1752
- default:
1753
- if (!textInputValidationAttributes.includes(key)) {
1754
- console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'number'.\x1b[0m`);
1755
- }
1756
- break;
1757
- }
1740
+ validationAttrs += ` ${key}="${value}"\n`;
1758
1741
  }
1759
1742
  } else {
1760
- console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'text'.\x1b[0m`);
1743
+ console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type 'textarea'.\x1b[0m`);
1761
1744
  }
1762
1745
  });
1763
1746
  }
1764
1747
 
1765
-
1766
-
1767
1748
  // Handle the binding syntax
1768
1749
  let bindingDirective = '';
1769
- if (attributes.binding) {
1770
- if (attributes.binding === 'bind:value' && name) {
1750
+ if (attributes.binding && name) {
1771
1751
  bindingDirective = `bind:value="${name}"\n`;
1772
1752
  }
1773
- if (attributes.binding.startsWith('::') && name) {
1774
- bindingDirective = `bind:value="${name}"\n`;
1775
- }
1776
- if (attributes.binding && !name) {
1777
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
1778
- return;
1779
- }
1780
- }
1781
-
1782
-
1783
1753
 
1784
- // Get the id from attributes or fall back to name
1785
1754
  let id = attributes.id || name;
1786
- // Determine if semanti is true based on formSettings
1787
1755
  const framework = this.formSettings?.framework || false;
1788
1756
 
1789
- // Construct additional attributes dynamically
1757
+ // Construct additional attributes (excluding internal keys and the 'value' content)
1790
1758
  let additionalAttrs = '';
1791
1759
  for (const [key, value] of Object.entries(attributes)) {
1792
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) { if (key.startsWith('on')) {
1793
- // Handle event attributes
1794
- if (framework === 'semantq') {
1795
- const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
1796
- additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
1797
- } else {
1798
- // Add parentheses if not present
1799
- const eventValue = value.endsWith('()') ? value : `${value}()`;
1800
- additionalAttrs += ` ${key}="${eventValue}"\n`;
1801
- }
1760
+ if (!['id', 'class', 'dependsOn', 'dependents', 'value', 'binding'].includes(key) && value !== undefined) {
1761
+ if (key.startsWith('on')) {
1762
+ const eventValue = value.endsWith('()') ? value : `${value}()`;
1763
+ additionalAttrs += ` ${key}="${eventValue}"\n`;
1802
1764
  } else {
1803
- // Handle boolean attributes
1765
+ const attrName = key.replace(/_/g, '-');
1804
1766
  if (value === true) {
1805
- additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
1767
+ additionalAttrs += ` ${attrName}\n`;
1806
1768
  } else if (value !== false) {
1807
- // Convert underscores to hyphens and set the attribute
1808
- additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
1769
+ additionalAttrs += ` ${attrName}="${value}"\n`;
1809
1770
  }
1810
1771
  }
1811
1772
  }
1812
1773
  }
1813
1774
 
1775
+ let inputClass = attributes.class || this.inputClass;
1814
1776
 
1777
+ // Build the raw HTML structure
1778
+ let formHTML = `
1779
+ <div class="${this.divClass}" id="${id + '-block'}">
1780
+ <label for="${id}">${label}
1781
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1782
+ </label>
1783
+ <textarea
1784
+ name="${name}"
1785
+ ${bindingDirective}
1786
+ id="${id}"
1787
+ class="${inputClass}"
1788
+ ${additionalAttrs}
1789
+ ${validationAttrs}
1790
+ ${(additionalAttrs.includes('placeholder') || attributes.placeholder) ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>${textareaValue}</textarea>
1791
+ </div>`.replace(/^\s*\n/gm, '').trim();
1792
+
1793
+ // Vertical layout formatting for attributes while preserving content
1794
+ let formattedHtml = formHTML.replace(/<textarea\s+([\s\S]*?)>([\s\S]*?)<\/textarea>/, (match, attrPart, content) => {
1795
+ // Split by whitespace only if it is NOT inside quotes (prevents stacking text/placeholders)
1796
+ const attrs = attrPart.trim()
1797
+ .split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/)
1798
+ .filter(a => a.trim() !== '')
1799
+ .map(attr => ` ${attr.trim()}`)
1800
+ .join('\n');
1801
+
1802
+ return `<textarea\n${attrs}\n>${content}</textarea>`;
1803
+ });
1815
1804
 
1816
- let inputClass;
1817
- if ('class' in attributes) {
1818
- inputClass = attributes.class;
1819
- } else {
1820
- inputClass = this.inputClass;
1821
- }
1822
-
1823
- // Construct the final HTML string for textarea
1824
- let formHTML = `
1825
- <div class="${this.divClass}" id="${id + '-block'}">
1826
- <label for="${id}">${label}
1827
- ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
1828
- </label>
1829
- <textarea
1830
- name="${name}"
1831
- ${bindingDirective}
1832
- id="${id}"
1833
- class="${inputClass}"
1834
- ${additionalAttrs}
1835
- ${validationAttrs}
1836
- ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
1837
- </textarea>
1838
- </div>
1839
- `.replace(/^\s*\n/gm, '').trim();
1840
-
1841
- let formattedHtml = formHTML;
1842
-
1843
- // Apply vertical layout to the <textarea> element only
1844
- formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
1845
- // Reformat attributes into a vertical layout
1846
- const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
1847
- return `<textarea\n${attributes}\n></textarea>`;
1848
- });
1849
-
1850
- this.formMarkUp += formattedHtml;
1851
-
1805
+ this.formMarkUp += formattedHtml;
1852
1806
  }
1853
1807
 
1854
-
1855
1808
  // New method for rendering tel fields
1856
1809
  renderTelField(type, name, label, validate, attributes) {
1857
1810
 
@@ -3409,97 +3362,7 @@ renderImageField(type, name, label, validate, attributes) {
3409
3362
 
3410
3363
 
3411
3364
 
3412
- // Textarea field rendering
3413
- renderTextAreaField(type, name, label, validate, attributes) {
3414
- const textAreaValidationAttributes = [
3415
- 'required',
3416
- 'minlength',
3417
- 'maxlength'
3418
- ];
3419
-
3420
- // Construct validation attributes
3421
- let validationAttrs = '';
3422
- if (validate) {
3423
- Object.entries(validate).forEach(([key, value]) => {
3424
- if (textAreaValidationAttributes.includes(key)) {
3425
- if (typeof value === 'boolean' && value) {
3426
- validationAttrs += ` ${key}\n`;
3427
- } else {
3428
- validationAttrs += ` ${key}="${value}"\n`;
3429
- }
3430
- } else {
3431
- console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
3432
- }
3433
- });
3434
- }
3435
-
3436
- // Handle the binding syntax
3437
- let bindingDirective = '';
3438
- if (attributes.binding) {
3439
- if (attributes.binding === 'bind:value' && name) {
3440
- bindingDirective = `bind:value="${name}"\n`;
3441
- }
3442
- if (attributes.binding.startsWith('::') && name) {
3443
- bindingDirective = `bind:value="${name}"\n`;
3444
- }
3445
- if (attributes.binding && !name) {
3446
- console.log(`\x1b[31m%s\x1b[0m`, `You cannot set binding value when there is no name attribute defined in ${name} ${type} field.`);
3447
- return;
3448
- }
3449
- }
3450
-
3451
- // Get the id from attributes or fall back to name
3452
- let id = attributes.id || name;
3453
-
3454
- // Construct additional attributes dynamically
3455
- let additionalAttrs = '';
3456
- for (const [key, value] of Object.entries(attributes)) {
3457
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
3458
- if (key.startsWith('on')) {
3459
- const eventValue = value.endsWith('()') ? value : `${value}()`;
3460
- additionalAttrs += ` ${key}="${eventValue}"\n`;
3461
- } else {
3462
- if (value === true) {
3463
- additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3464
- } else if (value !== false) {
3465
- additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3466
- }
3467
- }
3468
- }
3469
- }
3470
-
3471
- let inputClass = attributes.class || this.inputClass;
3472
-
3473
- // Construct the final HTML string
3474
- let formHTML = `
3475
- <div class="${this.divClass}" id="${id + '-block'}">
3476
- <label for="${id}">${label}
3477
- ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3478
- </label>
3479
- <textarea
3480
- name="${name}"
3481
- ${bindingDirective}
3482
- id="${id}"
3483
- class="${inputClass}"
3484
- ${additionalAttrs}
3485
- ${validationAttrs}
3486
- ${additionalAttrs.includes('placeholder') ? '' : (this.formSettings.placeholders ? `placeholder="${label}"` : '')}>
3487
- </textarea>
3488
- </div>
3489
- `.replace(/^\s*\n/gm, '').trim();
3490
-
3491
- let formattedHtml = formHTML;
3492
-
3493
- // Apply vertical layout to the <textarea> element only
3494
- formattedHtml = formattedHtml.replace(/<textarea\s+([^>]*)>\s*<\/textarea>/, (match, p1) => {
3495
- const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3496
- return `<textarea\n${attributes}\n></textarea>`;
3497
- });
3498
-
3499
- this.formMarkUp += formattedHtml;
3500
- }
3501
-
3502
-
3365
+ // Radio field rendering
3503
3366
 
3504
3367
  renderRadioField(type, name, label, validate, attributes, options) {
3505
3368
  // Define valid validation attributes for radio fields
@@ -3774,244 +3637,227 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3774
3637
  /* DYNAMIC SINGLE SELECT BLOCK */
3775
3638
 
3776
3639
  // Function to render the dynamic select field and update based on user selection
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
- }
3806
-
3807
- renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
3808
-
3809
- // Define valid validation attributes for select fields
3810
- const selectValidationAttributes = ['required'];
3811
-
3812
- // Construct validation attributes
3813
- let validationAttrs = '';
3814
- // Store original required state for the main select
3815
- let originalRequired = false; // <--- This variable tracks if the main select was originally required
3816
- if (validate) {
3817
- Object.entries(validate).forEach(([key, value]) => {
3818
- if (selectValidationAttributes.includes(key)) {
3819
- if (key === 'required') {
3820
- validationAttrs += `${key} `;
3821
- originalRequired = true; // Mark that it was originally required
3822
- }
3823
- } else {
3824
- // Removed console.warn
3825
- }
3640
+ renderDynamicSingleSelectField(type, name, label, validate, attributes, options, subOptions) {
3641
+ console.log('DEBUG: renderDynamicSingleSelectField called', {
3642
+ type, name, label,
3643
+ options: options ? options.length : 'none',
3644
+ subOptions: subOptions ? subOptions.length : 'none'
3826
3645
  });
3827
- }
3828
-
3829
- // Handle the binding syntax
3830
- let bindingDirective = '';
3831
- if (attributes.binding) {
3832
- if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
3833
- bindingDirective = ` bind:value="${name}" `;
3646
+
3647
+ // Check if options exist
3648
+ if (!options || !Array.isArray(options)) {
3649
+ console.warn('Dynamic single select field missing options:', name);
3650
+ options = [];
3834
3651
  }
3835
- }
3836
-
3837
- // Define attributes for the select field
3838
- let id = attributes.id || name;
3839
- let dimensionAttrs = ''; // No dimension attributes applicable for select fields
3840
-
3841
- // Handle additional attributes
3842
- let additionalAttrs = '';
3843
- for (const [key, value] of Object.entries(attributes)) {
3844
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
3845
- if (key.startsWith('on')) {
3846
- // Handle event attributes
3847
- const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3848
- additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3849
- } else {
3850
- // Handle boolean attributes
3851
- if (value === true) {
3852
- additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3853
- } else if (value !== false) {
3854
- // Convert underscores to hyphens and set the attribute
3855
- additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3652
+
3653
+ // Step 1: Extract main categories from options
3654
+ // Options should already be in the correct format from the parser
3655
+ const mainCategoryOptions = options.map(item => {
3656
+ // Handle both string and object formats
3657
+ if (typeof item === 'string') {
3658
+ return {
3659
+ value: item.toLowerCase().replace(/\s+/g, '-'),
3660
+ label: item
3661
+ };
3662
+ } else {
3663
+ // Already an object with value/label
3664
+ return {
3665
+ value: item.value || item.id || item,
3666
+ label: item.label || item.value || item
3667
+ };
3856
3668
  }
3857
- }
3669
+ });
3670
+
3671
+ // Step 2: Handle subCategoriesOptions (scenario blocks)
3672
+ let subCategoriesOptions = [];
3673
+
3674
+ if (subOptions && Array.isArray(subOptions)) {
3675
+ // Use the subOptions exactly as provided by the parser
3676
+ // They should already have the correct structure
3677
+ subCategoriesOptions = subOptions.map(item => {
3678
+ // Ensure each subCategory has the required structure
3679
+ return {
3680
+ id: item.id || item.value || '',
3681
+ label: item.label || item.id || '',
3682
+ options: Array.isArray(item.options) ? item.options : []
3683
+ };
3684
+ });
3858
3685
  }
3859
- }
3686
+
3687
+ console.log('Main categories:', mainCategoryOptions);
3688
+ console.log('Sub categories:', subCategoriesOptions);
3689
+
3690
+ // Pass both to the renderer with the mode flag
3691
+ this.renderSingleSelectField(
3692
+ type,
3693
+ name,
3694
+ label,
3695
+ validate,
3696
+ attributes,
3697
+ mainCategoryOptions,
3698
+ subCategoriesOptions,
3699
+ 'dynamicSingleSelect'
3700
+ );
3701
+ }
3860
3702
 
3861
- // Construct select options HTML based on options
3862
- let selectHTML = '';
3863
- if (Array.isArray(options)) {
3864
- // Add a default option
3865
- selectHTML += `
3866
- <option value="">Choose an option</option>
3867
- `;
3868
3703
 
3869
- // Add the provided options
3870
- selectHTML += options.map((option) => {
3871
- const isSelected = option.selected ? ' selected' : '';
3872
- return `
3873
- <option value="${option.value}"${isSelected}>${option.label}</option>
3874
- `;
3875
- }).join('');
3876
- }
3877
3704
 
3878
- let inputClass = attributes.class || this.inputClass;
3879
3705
 
3880
- // Remove `onchange` from HTML; it will be handled by JavaScript event listeners
3881
- const onchangeAttr = ''; // <--- Ensure this is an empty string
3882
3706
 
3883
- let labelDisplay;
3884
- let rawLabel;
3707
+ renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
3708
+ // Define valid validation attributes for select fields
3709
+ const selectValidationAttributes = ['required'];
3885
3710
 
3886
- if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
3887
- if (label.includes('-')) {
3888
- const [mainCategoryLabel] = label.split('-');
3889
- labelDisplay = mainCategoryLabel;
3890
- rawLabel = label;
3891
- } else {
3892
- labelDisplay = label;
3893
- rawLabel = label;
3711
+ // Construct validation attributes
3712
+ let validationAttrs = '';
3713
+ let originalRequired = false;
3714
+ if (validate) {
3715
+ Object.entries(validate).forEach(([key, value]) => {
3716
+ if (selectValidationAttributes.includes(key)) {
3717
+ if (key === 'required') {
3718
+ validationAttrs += `${key} `;
3719
+ originalRequired = true;
3720
+ }
3721
+ }
3722
+ });
3894
3723
  }
3895
- } else {
3896
- labelDisplay = label;
3897
- }
3898
-
3899
- // Construct the final HTML string for the main select
3900
- let formHTML = `
3901
- <fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
3902
- <legend>${labelDisplay}
3903
- ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3904
- </legend>
3905
- <label for="${id}"> Select ${labelDisplay}
3906
- <select name="${name}"
3907
- ${bindingDirective}
3908
- ${dimensionAttrs}
3909
- id="${id}"
3910
- class="${inputClass}"
3911
- ${additionalAttrs}
3912
- ${validationAttrs}
3913
- data-original-required="${originalRequired}" >
3914
- ${selectHTML}
3915
- </select>
3916
- </fieldset>
3917
- `.replace(/^\s*\n/gm, '').trim();
3918
-
3919
- // FIXED: Apply vertical layout to the <select> element and its children
3920
- // Only split on actual attribute boundaries, not within attribute values
3921
- let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3922
- // Use regex to match complete attribute="value" pairs
3923
- const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
3924
- const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
3925
- return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
3926
- });
3927
-
3928
- // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3929
- formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3930
- // Ensure <fieldset> starts on a new line
3931
- return `\n${match}\n`;
3932
- }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3933
-
3934
- this.formMarkUp+=formattedHtml;
3935
-
3936
3724
 
3937
- /* dynamicSingleSelect - Sub-Category Generation Block */
3938
-
3939
- if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
3940
-
3941
- const categoryId = attributes.id || name; // This is the ID of the main dynamic select ('languages')
3725
+ // Handle the binding syntax
3726
+ let bindingDirective = '';
3727
+ if (attributes.binding) {
3728
+ if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
3729
+ bindingDirective = ` bind:value="${name}" `;
3730
+ }
3731
+ }
3942
3732
 
3943
- subCategoriesOptions.forEach((subCategory) => {
3944
- const { id, label, options: subOptions } = subCategory; // Renamed 'options' to 'subOptions' to avoid conflict
3733
+ // Define attributes for the select field
3734
+ let id = attributes.id || name;
3735
+ let dimensionAttrs = '';
3945
3736
 
3946
- // IMPORTANT: Sub-category selects are *initially hidden*
3947
- // Therefore, by default, they are NOT required until they are revealed.
3948
- let isSubCategoryRequired = false; // Default to false as they are hidden
3949
- const subCategoryValidationAttrs = ''; // No direct 'required' in HTML initially
3737
+ // Handle additional attributes
3738
+ let additionalAttrs = '';
3739
+ for (const [key, value] of Object.entries(attributes)) {
3740
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
3741
+ if (key.startsWith('on')) {
3742
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3743
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3744
+ } else {
3745
+ if (value === true) {
3746
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3747
+ } else if (value !== false) {
3748
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3749
+ }
3750
+ }
3751
+ }
3752
+ }
3950
3753
 
3951
- // Build the select options HTML for sub-category
3952
- const subSelectHTML = subOptions.map(option => {
3953
- const isSelected = option.selected ? ' selected' : '';
3954
- return `
3955
- <option value="${option.value}"${isSelected}>${option.label}</option>
3754
+ // Construct select options HTML based on options
3755
+ let selectHTML = '';
3756
+ if (Array.isArray(options) && options.length > 0) {
3757
+ // Add a default option
3758
+ selectHTML += `
3759
+ <option value="">Choose an option</option>
3956
3760
  `;
3957
- }).join('');
3958
-
3959
-
3960
- let subCategoryLabel;
3961
-
3962
- if (rawLabel.includes('-')) {
3963
- subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
3964
- } else {
3965
- subCategoryLabel = 'options';
3966
- }
3967
3761
 
3968
- let optionsLabel;
3969
- if (subCategoryLabel !== 'options') {
3970
- optionsLabel = rawLabel.split('-')?.[1] + ' Option';
3971
- } else {
3972
- optionsLabel = subCategoryLabel;
3973
- }
3762
+ // Add the provided options
3763
+ selectHTML += options.map((option) => {
3764
+ const optionValue = option.value || option;
3765
+ const optionLabel = option.label || option;
3766
+ const isSelected = option.selected ? ' selected' : '';
3767
+ return `
3768
+ <option value="${optionValue}"${isSelected}>${optionLabel}</option>
3769
+ `;
3770
+ }).join('');
3771
+ }
3974
3772
 
3773
+ let inputClass = attributes.class || this.inputClass;
3975
3774
 
3976
- // Create the HTML for the sub-category fieldset and select elements
3977
- // Added a class based on the main select's ID for easy grouping/selection
3978
- let subFormHTML = `
3979
- <fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}" style="display: none;"> <legend>${label} ${subCategoryLabel} ${isSubCategoryRequired && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3980
- </legend>
3981
- <label for="${id}"> Select ${label} ${optionsLabel}
3982
- </label>
3983
- <select name="${id}"
3775
+ // Construct the final HTML string for the main select
3776
+ let formHTML = `
3777
+ <fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
3778
+ <legend>${label}
3779
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3780
+ </legend>
3781
+ <label for="${id}"> Select ${label}
3782
+ <select name="${name}"
3984
3783
  ${bindingDirective}
3985
3784
  ${dimensionAttrs}
3986
3785
  id="${id}"
3987
3786
  class="${inputClass}"
3988
3787
  ${additionalAttrs}
3989
- ${subCategoryValidationAttrs}
3990
- data-original-required="${isSubCategoryRequired}" >
3991
- <option value="">Choose an option</option>
3992
- ${subSelectHTML}
3993
- </select>
3994
- </fieldset>
3995
- `.replace(/^\s*\n/gm, '').trim();
3996
-
3997
- // FIXED: Apply the same corrected formatting to sub-category selects
3998
- subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3788
+ ${validationAttrs}
3789
+ data-original-required="${originalRequired}" >
3790
+ ${selectHTML}
3791
+ </select>
3792
+ </fieldset>
3793
+ `.replace(/^\s*\n/gm, '').trim();
3794
+
3795
+ // Format the HTML
3796
+ let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3999
3797
  const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
4000
3798
  const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
4001
3799
  return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
4002
- });
3800
+ });
4003
3801
 
4004
- // Ensure the <fieldset> block starts on a new line and remove extra blank lines
4005
- subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
4006
- return `\n${match}\n`;
4007
- }).replace(/\n\s*\n/g, '\n');
3802
+ this.formMarkUp += formattedHtml;
4008
3803
 
4009
- // Append the generated HTML to formMarkUp
4010
- this.formMarkUp += subFormHTML;
4011
- });
4012
- }
4013
- }
3804
+ /* dynamicSingleSelect - Sub-Category Generation Block */
3805
+ if (mode === 'dynamicSingleSelect' && subCategoriesOptions && Array.isArray(subCategoriesOptions) && subCategoriesOptions.length > 0) {
3806
+ const categoryId = attributes.id || name;
3807
+
3808
+ subCategoriesOptions.forEach((subCategory) => {
3809
+ // Skip invalid subCategories
3810
+ if (!subCategory || !subCategory.id) {
3811
+ console.warn('Invalid subCategory in dynamic select:', subCategory);
3812
+ return;
3813
+ }
3814
+
3815
+ const { id, label: subLabel, options: subOptions } = subCategory;
3816
+
3817
+ // Ensure subOptions is an array
3818
+ const subOptionArray = Array.isArray(subOptions) ? subOptions : [];
3819
+
3820
+ // Build the select options HTML for sub-category
3821
+ const subSelectHTML = subOptionArray.length > 0 ?
3822
+ subOptionArray.map(option => {
3823
+ const optionValue = option.value || option;
3824
+ const optionLabel = option.label || option;
3825
+ const isSelected = option.selected ? ' selected' : '';
3826
+ return `
3827
+ <option value="${optionValue}"${isSelected}>${optionLabel}</option>
3828
+ `;
3829
+ }).join('') :
3830
+ '<option value="">No options available</option>';
3831
+
3832
+ // Create the HTML for the sub-category fieldset
3833
+ let subFormHTML = `
3834
+ <fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}" style="display: none;">
3835
+ <legend>${subLabel || id}</legend>
3836
+ <label for="${id}"> Select ${subLabel || id}</label>
3837
+ <select name="${id}"
3838
+ ${bindingDirective}
3839
+ ${dimensionAttrs}
3840
+ id="${id}"
3841
+ class="${inputClass}"
3842
+ ${additionalAttrs}
3843
+ data-original-required="false">
3844
+ <option value="">Choose an option</option>
3845
+ ${subSelectHTML}
3846
+ </select>
3847
+ </fieldset>
3848
+ `.replace(/^\s*\n/gm, '').trim();
3849
+
3850
+ // Format the HTML
3851
+ subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3852
+ const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
3853
+ const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
3854
+ return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
3855
+ });
4014
3856
 
3857
+ this.formMarkUp += subFormHTML;
3858
+ });
3859
+ }
3860
+ }
4015
3861
 
4016
3862
 
4017
3863
  renderMultipleSelectField(type, name, label, validate, attributes, options) {