@formique/semantq 1.0.9 → 1.0.11

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.
@@ -64,7 +64,7 @@ class Formique extends FormBuilder {
64
64
 
65
65
  const formObjects = new astToFormique(ast);
66
66
 
67
- console.log("formSchema", JSON.stringify(formObjects.formSchema,null,2));
67
+ //console.log("formSchema", JSON.stringify(formObjects.formSchema,null,2));
68
68
  // Assign from formObjects if a string is passed
69
69
  formSchema = formObjects.formSchema;
70
70
  finalSettings = { ...formSettings, ...formObjects.formSettings };
@@ -133,10 +133,14 @@ class Formique extends FormBuilder {
133
133
  // Filter out 'submit' field for rendering, and render all other fields
134
134
  const nonSubmitFieldsHtml = this.formSchema
135
135
  .filter(field => field[0] !== 'submit')
136
- .map(field => {
137
- const [type, name, label, validate, attributes = {}, options] = field;
138
- return this.renderField(type, name, label, validate, attributes, options);
139
- }).join('');
136
+ .map(field => {
137
+ // FIX 1: Add 'subOptions' to capture the 7th element (Index 6)
138
+ const [type, name, label, validate, attributes = {}, options, subOptions] = field;
139
+
140
+ // FIX 2: Pass 'subOptions' through to renderField
141
+ return this.renderField(type, name, label, validate, attributes, options, subOptions);
142
+ }).join('');
143
+
140
144
  this.formMarkUp += nonSubmitFieldsHtml;
141
145
 
142
146
  // Find and render the submit button separately, at the very end of the form content
@@ -311,16 +315,48 @@ initDependencyGraph() {
311
315
  }
312
316
 
313
317
  // Attach Event Listeners
318
+ // Corrected Attach Event Listeners
314
319
  attachInputChangeListener(parentField) {
315
- const fieldElement = document.getElementById(parentField);
316
- //alert(parentField);
317
-
318
- if (fieldElement) {
319
- fieldElement.addEventListener('input', (event) => {
320
- const value = event.target.value;
321
- this.handleParentFieldChange(parentField, value);
322
- });
320
+ // Use querySelectorAll to get all elements with the name attribute matching the fieldId.
321
+ // This correctly targets all radio/checkbox inputs in a group.
322
+ const fieldElements = document.querySelectorAll(`[name="${parentField}"]`);
323
+
324
+ // If no elements found by name, fall back to getting the single element by ID
325
+ if (fieldElements.length === 0) {
326
+ const singleElement = document.getElementById(parentField);
327
+ if (singleElement) {
328
+ fieldElements = [singleElement]; // Treat it as a single element array
329
+ } else {
330
+ console.warn(`Parent field element(s) not found for field: ${parentField}`);
331
+ return;
332
+ }
323
333
  }
334
+
335
+ fieldElements.forEach(fieldElement => {
336
+ // Radio/checkbox groups should use 'change', not 'input'
337
+ const eventType = (fieldElement.type === 'radio' || fieldElement.type === 'checkbox') ? 'change' : 'input';
338
+
339
+ fieldElement.addEventListener(eventType, (event) => {
340
+ let value;
341
+ if (fieldElement.type === 'radio' && !event.target.checked) {
342
+ // Only process the change if the radio button is now checked
343
+ return;
344
+ }
345
+
346
+ if (fieldElement.type === 'checkbox') {
347
+ // For checkboxes, you might need special logic to return an array of checked values
348
+ // For now, let's stick to the change event on a single checkbox
349
+ value = event.target.checked ? event.target.value : '';
350
+ } else {
351
+ value = event.target.value;
352
+ }
353
+
354
+ // Convert value to lowercase for consistent comparison with 'yes' condition
355
+ //this.handleParentFieldChange(parentField, value.toLowerCase());
356
+
357
+ this.handleParentFieldChange(parentField, value.toLowerCase());
358
+ });
359
+ });
324
360
  }
325
361
 
326
362
 
@@ -426,7 +462,7 @@ attachDynamicSelectListeners() {
426
462
  const selectedCategory = event.target.value; // e.g., 'frontend', 'backend', 'server'
427
463
 
428
464
  // Find all sub-category fieldsets related to this main select
429
- const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}-subcategory-group`);
465
+ const subCategoryFieldsets = document.querySelectorAll(`.${mainSelectId}`);
430
466
 
431
467
  subCategoryFieldsets.forEach(fieldset => {
432
468
  const subSelect = fieldset.querySelector('select'); // Get the actual select element
@@ -439,7 +475,7 @@ attachDynamicSelectListeners() {
439
475
  });
440
476
 
441
477
  // Show the selected sub-category fieldset and manage its required state
442
- const selectedFieldsetId = selectedCategory + '-options'; // Matches the ID format in renderSingleSelectField
478
+ const selectedFieldsetId = selectedCategory; // Matches the ID format in renderSingleSelectField
443
479
  const selectedFieldset = document.getElementById(selectedFieldsetId);
444
480
 
445
481
  if (selectedFieldset) {
@@ -694,51 +730,10 @@ renderForm() {
694
730
  }
695
731
 
696
732
 
697
- /*
698
- renderField(type, name, label, validate, attributes, options) {
699
- const fieldRenderMap = {
700
- 'text': this.renderTextField,
701
- 'email': this.renderEmailField,
702
- 'number': this.renderNumberField,
703
- 'password': this.renderPasswordField,
704
- 'textarea': this.renderTextAreaField,
705
- 'tel': this.renderTelField,
706
- 'date': this.renderDateField,
707
- 'time': this.renderTimeField,
708
- 'datetime-local': this.renderDateTimeField,
709
- 'month': this.renderMonthField,
710
- 'week': this.renderWeekField,
711
- 'url': this.renderUrlField,
712
- 'search': this.renderSearchField,
713
- 'color': this.renderColorField,
714
- 'checkbox': this.renderCheckboxField,
715
- 'radio': this.renderRadioField,
716
- 'file': this.renderFileField,
717
- 'hidden': this.renderHiddenField,
718
- 'image': this.renderImageField,
719
- 'textarea': this.renderTextAreaField,
720
- 'singleSelect': this.renderSingleSelectField,
721
- 'multipleSelect': this.renderMultipleSelectField,
722
- 'dynamicSingleSelect': this.renderDynamicSingleSelectField,
723
- 'range': this.renderRangeField,
724
- 'submit': this.renderSubmitButton,
725
- };
726
-
727
- const renderMethod = fieldRenderMap[type];
728
-
729
- if (renderMethod) {
730
- return renderMethod.call(this, type, name, label, validate, attributes, options);
731
- } else {
732
- console.warn(`Unsupported field type '${type}' encountered.`);
733
- return ''; // or handle gracefully
734
- }
735
- }
736
-
737
- */
738
733
 
739
734
 
740
735
  // renderField method - No change needed here for this issue, but ensure it handles 'submit' type correctly if called directly
741
- renderField(type, name, label, validate, attributes, options) {
736
+ renderField(type, name, label, validate, attributes, options, subOptions = undefined) {
742
737
  const fieldRenderMap = {
743
738
  'text': this.renderTextField,
744
739
  'email': this.renderEmailField,
@@ -773,7 +768,7 @@ renderField(type, name, label, validate, attributes, options) {
773
768
  // If the type is 'submit', ensure we use the specific renderSubmitButtonElement
774
769
  // Although, with the filter in renderForm(), this branch for 'submit' type
775
770
  // might not be hit in the primary rendering flow, it's good practice.
776
- return renderMethod.call(this, type, name, label, validate, attributes, options);
771
+ return renderMethod.call(this, type, name, label, validate, attributes, options,subOptions);
777
772
 
778
773
  if (type === 'submit') {
779
774
  return this.renderSubmitButton(type, name, label, validate, attributes, options);
@@ -3731,239 +3726,240 @@ renderCheckboxField(type, name, label, validate, attributes, options) {
3731
3726
  /* DYNAMIC SINGLE SELECT BLOCK */
3732
3727
 
3733
3728
  // Function to render the dynamic select field and update based on user selection
3734
- renderDynamicSingleSelectField(type, name, label, validate, attributes, options) {
3735
-
3736
- // Step 1: Transform the data into an array of objects
3737
- const mainCategoryOptions = options.flat().map(item => {
3738
- // Check if any option has selected: true
3739
- const selected = item.options.some(option => option.selected === true);
3740
-
3741
- // Create a transformed object
3742
- return {
3743
- value: item.id,
3744
- label: item.label,
3745
- ...(selected && { selected: true }) // Conditionally add selected: true
3746
- };
3747
- });
3748
-
3749
- const subCategoriesOptions=options;
3750
- const mode='dynamicSingleSelect';
3751
- this.renderSingleSelectField(type, name, label, validate, attributes, mainCategoryOptions, subCategoriesOptions, mode);
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
+ });
3752
3745
 
3753
- }
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
+
3754
3752
 
3755
3753
 
3756
3754
  renderSingleSelectField(type, name, label, validate, attributes, options, subCategoriesOptions, mode) {
3757
3755
 
3758
- console.log("Within renderSingleSelectField");
3759
- // Define valid validation attributes for select fields
3760
- const selectValidationAttributes = ['required'];
3761
-
3762
- // Construct validation attributes
3763
- let validationAttrs = '';
3764
- // Store original required state for the main select
3765
- let originalRequired = false; // <--- This variable tracks if the main select was originally required
3766
- if (validate) {
3767
- Object.entries(validate).forEach(([key, value]) => {
3768
- if (selectValidationAttributes.includes(key)) {
3769
- if (key === 'required') {
3770
- validationAttrs += `${key} `;
3771
- originalRequired = true; // Mark that it was originally required
3772
- }
3773
- } else {
3774
- console.warn(`\x1b[31mUnsupported validation attribute '${key}' for field '${name}' of type '${type}'.\x1b[0m`);
3775
- }
3776
- });
3777
- }
3756
+ // Define valid validation attributes for select fields
3757
+ const selectValidationAttributes = ['required'];
3778
3758
 
3779
- // Handle the binding syntax
3780
- let bindingDirective = '';
3781
- if (attributes.binding) {
3782
- if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
3783
- bindingDirective = ` bind:value="${name}" `;
3759
+ // Construct validation attributes
3760
+ let validationAttrs = '';
3761
+ // Store original required state for the main select
3762
+ let originalRequired = false; // <--- This variable tracks if the main select was originally required
3763
+ if (validate) {
3764
+ Object.entries(validate).forEach(([key, value]) => {
3765
+ if (selectValidationAttributes.includes(key)) {
3766
+ if (key === 'required') {
3767
+ validationAttrs += `${key} `;
3768
+ originalRequired = true; // Mark that it was originally required
3784
3769
  }
3770
+ } else {
3771
+ // Removed console.warn
3772
+ }
3773
+ });
3774
+ }
3775
+
3776
+ // Handle the binding syntax
3777
+ let bindingDirective = '';
3778
+ if (attributes.binding) {
3779
+ if (typeof attributes.binding === 'string' && attributes.binding.startsWith('::')) {
3780
+ bindingDirective = ` bind:value="${name}" `;
3785
3781
  }
3782
+ }
3786
3783
 
3787
- // Define attributes for the select field
3788
- let id = attributes.id || name;
3789
- let dimensionAttrs = ''; // No dimension attributes applicable for select fields
3784
+ // Define attributes for the select field
3785
+ let id = attributes.id || name;
3786
+ let dimensionAttrs = ''; // No dimension attributes applicable for select fields
3790
3787
 
3791
- // Handle additional attributes
3792
- let additionalAttrs = '';
3793
- for (const [key, value] of Object.entries(attributes)) {
3794
- if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
3795
- if (key.startsWith('on')) {
3796
- // Handle event attributes
3797
- const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3798
- additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3799
- } else {
3800
- // Handle boolean attributes
3801
- if (value === true) {
3802
- additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3803
- } else if (value !== false) {
3804
- // Convert underscores to hyphens and set the attribute
3805
- additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3806
- }
3807
- }
3788
+ // Handle additional attributes
3789
+ let additionalAttrs = '';
3790
+ for (const [key, value] of Object.entries(attributes)) {
3791
+ if (key !== 'id' && key !== 'class' && key !== 'dependsOn' && key !== 'dependents' && value !== undefined) {
3792
+ if (key.startsWith('on')) {
3793
+ // Handle event attributes
3794
+ const eventValue = value.endsWith('()') ? value.slice(0, -2) : value;
3795
+ additionalAttrs += ` @${key.replace(/^on/, '')}={${eventValue}}\n`;
3796
+ } else {
3797
+ // Handle boolean attributes
3798
+ if (value === true) {
3799
+ additionalAttrs += ` ${key.replace(/_/g, '-')}\n`;
3800
+ } else if (value !== false) {
3801
+ // Convert underscores to hyphens and set the attribute
3802
+ additionalAttrs += ` ${key.replace(/_/g, '-')}="${value}"\n`;
3808
3803
  }
3804
+ }
3809
3805
  }
3806
+ }
3810
3807
 
3811
- // Construct select options HTML based on options
3812
- let selectHTML = '';
3813
- if (Array.isArray(options)) {
3814
- // Add a default option
3815
- selectHTML += `
3816
- <option value="">Choose an option</option>
3817
- `;
3808
+ // Construct select options HTML based on options
3809
+ let selectHTML = '';
3810
+ if (Array.isArray(options)) {
3811
+ // Add a default option
3812
+ selectHTML += `
3813
+ <option value="">Choose an option</option>
3814
+ `;
3818
3815
 
3819
- // Add the provided options
3820
- selectHTML += options.map((option) => {
3821
- const isSelected = option.selected ? ' selected' : '';
3822
- return `
3823
- <option value="${option.value}"${isSelected}>${option.label}</option>
3824
- `;
3825
- }).join('');
3826
- }
3816
+ // Add the provided options
3817
+ selectHTML += options.map((option) => {
3818
+ const isSelected = option.selected ? ' selected' : '';
3819
+ return `
3820
+ <option value="${option.value}"${isSelected}>${option.label}</option>
3821
+ `;
3822
+ }).join('');
3823
+ }
3827
3824
 
3828
- let inputClass = attributes.class || this.inputClass;
3825
+ let inputClass = attributes.class || this.inputClass;
3829
3826
 
3830
- // Remove `onchange` from HTML; it will be handled by JavaScript event listeners
3831
- const onchangeAttr = ''; // <--- Ensure this is an empty string
3827
+ // Remove `onchange` from HTML; it will be handled by JavaScript event listeners
3828
+ const onchangeAttr = ''; // <--- Ensure this is an empty string
3832
3829
 
3833
- let labelDisplay;
3834
- let rawLabel;
3830
+ let labelDisplay;
3831
+ let rawLabel;
3835
3832
 
3836
- if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
3837
- if (label.includes('-')) {
3838
- const [mainCategoryLabel] = label.split('-');
3839
- labelDisplay = mainCategoryLabel;
3840
- rawLabel = label;
3841
- } else {
3842
- labelDisplay = label;
3843
- rawLabel = label;
3844
- }
3833
+ if (mode === 'dynamicSingleSelect' && subCategoriesOptions) {
3834
+ if (label.includes('-')) {
3835
+ const [mainCategoryLabel] = label.split('-');
3836
+ labelDisplay = mainCategoryLabel;
3837
+ rawLabel = label;
3845
3838
  } else {
3846
- labelDisplay = label;
3839
+ labelDisplay = label;
3840
+ rawLabel = label;
3847
3841
  }
3842
+ } else {
3843
+ labelDisplay = label;
3844
+ }
3848
3845
 
3849
-
3850
- // Construct the final HTML string for the main select
3851
- let formHTML = `
3852
- <fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
3853
- <legend>${labelDisplay}
3854
- ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3855
- </legend>
3856
- <label for="${id}"> Select ${labelDisplay}
3857
- <select name="${name}"
3858
- ${bindingDirective}
3859
- ${dimensionAttrs}
3860
- id="${id}"
3861
- class="${inputClass}"
3862
- ${additionalAttrs}
3863
- ${validationAttrs}
3864
- data-original-required="${originalRequired}" >
3865
- ${selectHTML}
3866
- </select>
3867
- </fieldset>
3846
+ // Construct the final HTML string for the main select
3847
+ let formHTML = `
3848
+ <fieldset class="${this.selectGroupClass}" id="${id + '-block'}">
3849
+ <legend>${labelDisplay}
3850
+ ${validationAttrs.includes('required') && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3851
+ </legend>
3852
+ <label for="${id}"> Select ${labelDisplay}
3853
+ <select name="${name}"
3854
+ ${bindingDirective}
3855
+ ${dimensionAttrs}
3856
+ id="${id}"
3857
+ class="${inputClass}"
3858
+ ${additionalAttrs}
3859
+ ${validationAttrs}
3860
+ data-original-required="${originalRequired}" >
3861
+ ${selectHTML}
3862
+ </select>
3863
+ </fieldset>
3868
3864
  `.replace(/^\s*\n/gm, '').trim();
3869
3865
 
3866
+ // FIXED: Apply vertical layout to the <select> element and its children
3867
+ // Only split on actual attribute boundaries, not within attribute values
3868
+ let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3869
+ // Use regex to match complete attribute="value" pairs
3870
+ const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
3871
+ const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
3872
+ return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
3873
+ });
3870
3874
 
3871
- // Apply vertical layout to the <select> element and its children
3872
- let formattedHtml = formHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3873
- // Reformat attributes into a vertical layout
3874
- const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3875
- return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
3876
- });
3875
+ // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3876
+ formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3877
+ // Ensure <fieldset> starts on a new line
3878
+ return `\n${match}\n`;
3879
+ }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3877
3880
 
3878
- // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3879
- formattedHtml = formattedHtml.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3880
- // Ensure <fieldset> starts on a new line
3881
- return `\n${match}\n`;
3882
- }).replace(/\n\s*\n/g, '\n'); // Remove extra blank lines
3881
+ this.formMarkUp+=formattedHtml;
3883
3882
 
3884
- this.formMarkUp+=formattedHtml;
3885
3883
 
3884
+ /* dynamicSingleSelect - Sub-Category Generation Block */
3886
3885
 
3887
- /* dynamicSingleSelect - Sub-Category Generation Block */
3886
+ if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
3888
3887
 
3889
- if (mode && mode ==='dynamicSingleSelect' && subCategoriesOptions) {
3888
+ const categoryId = attributes.id || name; // This is the ID of the main dynamic select ('languages')
3890
3889
 
3891
- const categoryId = attributes.id || name; // This is the ID of the main dynamic select ('languages')
3890
+ subCategoriesOptions.forEach((subCategory) => {
3891
+ const { id, label, options: subOptions } = subCategory; // Renamed 'options' to 'subOptions' to avoid conflict
3892
3892
 
3893
- subCategoriesOptions.forEach(subCategory => {
3894
- const { id, label, options: subOptions } = subCategory; // Renamed 'options' to 'subOptions' to avoid conflict
3893
+ // IMPORTANT: Sub-category selects are *initially hidden*
3894
+ // Therefore, by default, they are NOT required until they are revealed.
3895
+ let isSubCategoryRequired = false; // Default to false as they are hidden
3896
+ const subCategoryValidationAttrs = ''; // No direct 'required' in HTML initially
3895
3897
 
3896
- // IMPORTANT: Sub-category selects are *initially hidden*
3897
- // Therefore, by default, they are NOT required until they are revealed.
3898
- // If your schema later allows specific sub-categories to be inherently required
3899
- // when shown, you'd need to extract that validation from your schema here.
3900
- // For now, they are considered non-required until JavaScript makes them required.
3901
- let isSubCategoryRequired = false; // Default to false as they are hidden
3902
- const subCategoryValidationAttrs = ''; // No direct 'required' in HTML initially
3898
+ // Build the select options HTML for sub-category
3899
+ const subSelectHTML = subOptions.map(option => {
3900
+ const isSelected = option.selected ? ' selected' : '';
3901
+ return `
3902
+ <option value="${option.value}"${isSelected}>${option.label}</option>
3903
+ `;
3904
+ }).join('');
3903
3905
 
3904
- // Build the select options HTML for sub-category
3905
- const subSelectHTML = subOptions.map(option => {
3906
- const isSelected = option.selected ? ' selected' : '';
3907
- return `
3908
- <option value="${option.value}"${isSelected}>${option.label}</option>
3909
- `;
3910
- }).join('');
3911
3906
 
3907
+ let subCategoryLabel;
3908
+
3909
+ if (rawLabel.includes('-')) {
3910
+ subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
3911
+ } else {
3912
+ subCategoryLabel = 'options';
3913
+ }
3912
3914
 
3913
- let subCategoryLabel;
3914
- console.log('Label (rawLabel for sub-category):', rawLabel); // Debug log
3915
+ let optionsLabel;
3916
+ if (subCategoryLabel !== 'options') {
3917
+ optionsLabel = rawLabel.split('-')?.[1] + ' Option';
3918
+ } else {
3919
+ optionsLabel = subCategoryLabel;
3920
+ }
3915
3921
 
3916
- if (rawLabel.includes('-')) {
3917
- subCategoryLabel = rawLabel.split('-')?.[1] + ' Options';
3918
- } else {
3919
- subCategoryLabel = 'options';
3920
- }
3921
3922
 
3922
- let optionsLabel;
3923
- if (subCategoryLabel !== 'options') {
3924
- optionsLabel = rawLabel.split('-')?.[1] + ' Option';
3925
- } else {
3926
- optionsLabel = subCategoryLabel;
3927
- }
3923
+ // Create the HTML for the sub-category fieldset and select elements
3924
+ // Added a class based on the main select's ID for easy grouping/selection
3925
+ let subFormHTML = `
3926
+ <fieldset class="${this.selectGroupClass} ${categoryId}" id="${id}" style="display: none;"> <legend>${label} ${subCategoryLabel} ${isSubCategoryRequired && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3927
+ </legend>
3928
+ <label for="${id}"> Select ${label} ${optionsLabel}
3929
+ </label>
3930
+ <select name="${id}"
3931
+ ${bindingDirective}
3932
+ ${dimensionAttrs}
3933
+ id="${id}"
3934
+ class="${inputClass}"
3935
+ ${additionalAttrs}
3936
+ ${subCategoryValidationAttrs}
3937
+ data-original-required="${isSubCategoryRequired}" >
3938
+ <option value="">Choose an option</option>
3939
+ ${subSelectHTML}
3940
+ </select>
3941
+ </fieldset>
3942
+ `.replace(/^\s*\n/gm, '').trim();
3943
+
3944
+ // FIXED: Apply the same corrected formatting to sub-category selects
3945
+ subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3946
+ const attributes = p1.match(/(\w+(?:-\w+)*=("[^"]*"|'[^']*'|\w+)|[^=\s]+(?!\s*=))/g) || [];
3947
+ const formattedAttributes = attributes.map(attr => ` ${attr}`).join('\n');
3948
+ return `<select\n${formattedAttributes}\n>\n${p2.trim()}\n</select>`;
3949
+ });
3928
3950
 
3951
+ // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3952
+ subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3953
+ return `\n${match}\n`;
3954
+ }).replace(/\n\s*\n/g, '\n');
3929
3955
 
3930
- // Create the HTML for the sub-category fieldset and select elements
3931
- // Added a class based on the main select's ID for easy grouping/selection
3932
- let subFormHTML = `
3933
- <fieldset class="${this.selectGroupClass} ${categoryId}-subcategory-group" id="${id}-options" style="display: none;"> <legend>${label} ${subCategoryLabel} ${isSubCategoryRequired && this.formSettings.requiredFieldIndicator ? this.formSettings.asteriskHtml : ''}
3934
- </legend>
3935
- <label for="${id}"> Select ${label} ${optionsLabel}
3936
- </label>
3937
- <select name="${id}"
3938
- ${bindingDirective}
3939
- ${dimensionAttrs}
3940
- id="${id}"
3941
- class="${inputClass}"
3942
- ${additionalAttrs}
3943
- ${subCategoryValidationAttrs}
3944
- data-original-required="${isSubCategoryRequired}" >
3945
- <option value="">Choose an option</option>
3946
- ${subSelectHTML}
3947
- </select>
3948
- </fieldset>
3949
- `.replace(/^\s*\n/gm, '').trim();
3950
-
3951
- // Apply vertical layout to the <select> element and its children
3952
- subFormHTML = subFormHTML.replace(/<select\s+([^>]*)>([\s\S]*?)<\/select>/g, (match, p1, p2) => {
3953
- const attributes = p1.trim().split(/\s+/).map(attr => ` ${attr}`).join('\n');
3954
- return `<select\n${attributes}\n>\n${p2.trim()}\n</select>`;
3955
- });
3956
+ // Append the generated HTML to formMarkUp
3957
+ this.formMarkUp += subFormHTML;
3958
+ });
3959
+ }
3960
+ }
3956
3961
 
3957
- // Ensure the <fieldset> block starts on a new line and remove extra blank lines
3958
- subFormHTML = subFormHTML.replace(/(<fieldset\s+[^>]*>)/g, (match) => {
3959
- return `\n${match}\n`;
3960
- }).replace(/\n\s*\n/g, '\n');
3961
3962
 
3962
- // Append the generated HTML to formMarkUp
3963
- this.formMarkUp += subFormHTML;
3964
- });
3965
- }
3966
- }
3967
3963
 
3968
3964
  renderMultipleSelectField(type, name, label, validate, attributes, options) {
3969
3965
  // Define valid validation attributes for multiple select fields
@@ -4181,6 +4177,7 @@ renderRecaptchaField(type, name, label, validate, attributes = {}) {
4181
4177
  }
4182
4178
 
4183
4179
 
4180
+
4184
4181
  /*
4185
4182
  renderRangeField(type, name, label, validate, attributes) {
4186
4183
  const rangeValidationAttributes = ['required', 'min', 'max', 'step'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formique/semantq",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Formique is a native form builder for the Semantq JS Framework",
5
5
  "main": "formique-semantq.js",
6
6
  "type": "module",