ultimate-jekyll-manager 0.0.150 → 0.0.151

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.
package/CLAUDE.md CHANGED
@@ -629,7 +629,8 @@ initializing → ready ⇄ submitting → ready (or submitted)
629
629
  resetOnSuccess: false, // Clear form fields after successful submission
630
630
  warnOnUnsavedChanges: false, // Warn user before leaving with unsaved changes
631
631
  submittingText: 'Processing...', // Text shown on submit button during submission
632
- submittedText: 'Processed!' // Text shown on submit button after success (when allowResubmit: false)
632
+ submittedText: 'Processed!', // Text shown on submit button after success (when allowResubmit: false)
633
+ inputGroup: null // Filter getData() by data-input-group attribute (null = all fields)
633
634
  }
634
635
  ```
635
636
 
@@ -641,6 +642,7 @@ initializing → ready ⇄ submitting → ready (or submitted)
641
642
  | `validation` | `{ data, setError }` | Custom validation before submit |
642
643
  | `change` | `{ field, name, value, data }` | Field value changed |
643
644
  | `statechange` | `{ state, previousState }` | State transition |
645
+ | `honeypot` | `{ data }` | Honeypot triggered (for spam tracking) |
644
646
 
645
647
  **Validation System:**
646
648
 
@@ -668,8 +670,10 @@ When the form transitions to `ready` state, FormManager automatically focuses th
668
670
  |--------|-------------|
669
671
  | `on(event, callback)` | Register event listener (chainable) |
670
672
  | `ready()` | Transition to ready state |
671
- | `getData()` | Get form data as nested object (supports dot notation) |
673
+ | `getData()` | Get form data as nested object (supports dot notation, respects input group filter) |
672
674
  | `setData(obj)` | Set form values from nested object |
675
+ | `setInputGroup(group)` | Set input group filter (string, array, or null) |
676
+ | `getInputGroup()` | Get current input group filter |
673
677
  | `showSuccess(msg)` | Show success notification |
674
678
  | `showError(msg)` | Show error notification |
675
679
  | `reset()` | Reset form and go to ready state |
@@ -690,6 +694,55 @@ Results in:
690
694
  { user: { address: { city: 'NYC' } } }
691
695
  ```
692
696
 
697
+ **Input Groups:**
698
+
699
+ Filter `getData()` to only return fields matching a specific group. Fields without `data-input-group` are "global" and always included.
700
+
701
+ ```html
702
+ <!-- Global fields (no data-input-group) - always included -->
703
+ <input name="settings.theme" value="dark">
704
+
705
+ <!-- Group-specific fields -->
706
+ <input name="options.url" data-input-group="url" value="https://example.com">
707
+ <input name="options.ssid" data-input-group="wifi" value="MyWiFi">
708
+ <input name="options.password" data-input-group="wifi" value="secret123">
709
+ ```
710
+
711
+ ```javascript
712
+ // Set group filter (accepts string or array)
713
+ formManager.setInputGroup('url'); // Single group
714
+ formManager.setInputGroup(['url', 'wifi']); // Multiple groups
715
+ formManager.setInputGroup(null); // Clear filter (all fields)
716
+
717
+ // Get current filter
718
+ formManager.getInputGroup(); // Returns ['url'] or null
719
+
720
+ // getData() respects the filter
721
+ formManager.setInputGroup('wifi');
722
+ formManager.getData();
723
+ // Returns: { settings: { theme: 'dark' }, options: { ssid: 'MyWiFi', password: 'secret123' } }
724
+ // Note: 'url' field excluded, global 'settings.theme' included
725
+ ```
726
+
727
+ Can also be set via config:
728
+ ```javascript
729
+ const fm = new FormManager('#form', { inputGroup: 'wifi' });
730
+ ```
731
+
732
+ **Honeypot (Bot Detection):**
733
+
734
+ FormManager automatically rejects submissions if a honeypot field is filled. Honeypot fields are hidden from users but bots fill them automatically.
735
+
736
+ ```html
737
+ <!-- Hidden from users via CSS -->
738
+ <input type="text" name="honey" autocomplete="off" tabindex="-1"
739
+ style="position: absolute; left: -9999px;" aria-hidden="true">
740
+ ```
741
+
742
+ Fields matching `[data-honey]` or `[name="honey"]` are:
743
+ - Excluded from `getData()` output
744
+ - Checked during validation — if filled, submission is rejected with generic error
745
+
693
746
  **Checkbox Handling:**
694
747
  - **Single checkbox:** Returns `true`/`false`
695
748
  - **Checkbox group (same name):** Returns object `{ value1: true, value2: false }`
package/README.md CHANGED
@@ -449,6 +449,7 @@ initializing → ready ⇄ submitting → ready (or submitted)
449
449
  | `validation` | `{ data, setError }` | Custom validation before submit. Use `setError(fieldName, message)` to add errors. |
450
450
  | `change` | `{ field, name, value, data }` | Called when any field value changes. |
451
451
  | `statechange` | `{ state, previousState }` | Called when form state changes. |
452
+ | `honeypot` | `{ data }` | Called when honeypot is triggered (for spam tracking). |
452
453
 
453
454
  **Validation:**
454
455
 
@@ -518,6 +519,16 @@ Produces:
518
519
  }
519
520
  ```
520
521
 
522
+ **Honeypot (Bot Detection):**
523
+
524
+ FormManager automatically rejects submissions if a honeypot field is filled. Fields matching `[data-honey]` or `[name="honey"]` are excluded from `getData()` and trigger rejection if filled.
525
+
526
+ ```html
527
+ <!-- Hidden from users via CSS -->
528
+ <input type="text" name="honey" autocomplete="off" tabindex="-1"
529
+ style="position: absolute; left: -9999px;" aria-hidden="true">
530
+ ```
531
+
521
532
  **Checkbox Handling:**
522
533
 
523
534
  - **Single checkbox:** Returns `true` or `false`
@@ -19,8 +19,8 @@ export default function (Manager, options) {
19
19
 
20
20
  // Wait for DOM to be ready
21
21
  webManager.dom().ready().then(() => {
22
- // Setup mouse leave detection without needing the element yet
23
- setupMouseLeaveDetection();
22
+ // Setup exit intent detection without needing the element yet
23
+ setupExitIntentDetection();
24
24
  });
25
25
 
26
26
  // Make this available on window object in DEV mode for testing
@@ -82,8 +82,8 @@ export default function (Manager, options) {
82
82
  $modal.removeAttribute('hidden');
83
83
  }
84
84
 
85
- function setupMouseLeaveDetection() {
86
- // Detect when mouse leaves document
85
+ function setupExitIntentDetection() {
86
+ // 1. Mouse leave detection (desktop) - leaving from the top
87
87
  document.addEventListener('mouseleave', (e) => {
88
88
  /* @dev-only:start */
89
89
  // {
@@ -91,13 +91,74 @@ export default function (Manager, options) {
91
91
  // }
92
92
  /* @dev-only:end */
93
93
 
94
- // Only trigger if:
95
- // 1. We should show the popup (based on timing/session)
96
- // 2. Mouse is leaving from the top (Y <= 0 means exiting from top)
94
+ // Only trigger if mouse is leaving from the top (Y <= 0)
97
95
  if (shouldShow && e.clientY <= 0) {
98
96
  showExitPopup();
99
97
  }
100
98
  });
99
+
100
+ // 2. Window blur detection - user switches tabs or clicks outside browser
101
+ window.addEventListener('blur', () => {
102
+ /* @dev-only:start */
103
+ // {
104
+ // console.log('Window blur detected:', shouldShow);
105
+ // }
106
+ /* @dev-only:end */
107
+
108
+ if (shouldShow) {
109
+ showExitPopup();
110
+ }
111
+ });
112
+
113
+ // // 3. Back button detection - user attempts to navigate back
114
+ // // Push a dummy state so we can detect when user tries to go back
115
+ // history.pushState({ exitPopup: true }, '');
116
+ // window.addEventListener('popstate', () => {
117
+ // /* @dev-only:start */
118
+ // // {
119
+ // // console.log('Popstate detected:', shouldShow);
120
+ // // }
121
+ // /* @dev-only:end */
122
+
123
+ // if (shouldShow) {
124
+ // // Re-push state to prevent actual navigation
125
+ // history.pushState({ exitPopup: true }, '');
126
+ // showExitPopup();
127
+ // }
128
+ // });
129
+
130
+ // // 4. Mobile exit intent - rapid scroll up toward the top of the page
131
+ // let lastScrollY = window.scrollY;
132
+ // let scrollVelocity = 0;
133
+ // let lastScrollTime = Date.now();
134
+
135
+ // window.addEventListener('scroll', () => {
136
+ // const now = Date.now();
137
+ // const deltaTime = now - lastScrollTime;
138
+ // const deltaY = lastScrollY - window.scrollY; // Positive = scrolling up
139
+
140
+ // // Calculate velocity (pixels per ms)
141
+ // scrollVelocity = deltaTime > 0 ? deltaY / deltaTime : 0;
142
+
143
+ // /* @dev-only:start */
144
+ // // {
145
+ // // if (scrollVelocity > 1) {
146
+ // // console.log('Scroll velocity:', scrollVelocity, 'scrollY:', window.scrollY);
147
+ // // }
148
+ // // }
149
+ // /* @dev-only:end */
150
+
151
+ // // Trigger if:
152
+ // // - Scrolling up rapidly (velocity > 2 pixels/ms)
153
+ // // - Near the top of the page (within 100px)
154
+ // // - Should show popup
155
+ // if (shouldShow && scrollVelocity > 2 && window.scrollY < 100) {
156
+ // showExitPopup();
157
+ // }
158
+
159
+ // lastScrollY = window.scrollY;
160
+ // lastScrollTime = now;
161
+ // }, { passive: true });
101
162
  }
102
163
 
103
164
  function showExitPopup() {
@@ -15,6 +15,9 @@
15
15
  import { ready as domReady } from 'web-manager/modules/dom.js';
16
16
  import { showNotification, getDeviceType } from 'web-manager/modules/utilities.js';
17
17
 
18
+ // Constants
19
+ const HONEYPOT_SELECTOR = '[data-honey], [name="honey"]';
20
+
18
21
  export class FormManager {
19
22
  constructor(selector, options = {}) {
20
23
  // Get form element
@@ -35,6 +38,7 @@ export class FormManager {
35
38
  warnOnUnsavedChanges: false, // Warn user before leaving page with unsaved changes
36
39
  submittingText: 'Processing...', // Text shown on submit button during submission
37
40
  submittedText: 'Processed!', // Text shown on submit button after submission (when allowResubmit: false)
41
+ inputGroup: null, // Filter getData() to only include fields with matching data-input-group (null = all fields)
38
42
  ...options,
39
43
  };
40
44
 
@@ -48,6 +52,7 @@ export class FormManager {
48
52
  validation: [],
49
53
  submit: [],
50
54
  statechange: [],
55
+ honeypot: [],
51
56
  };
52
57
 
53
58
  // Field errors (populated during validation)
@@ -297,6 +302,21 @@ export class FormManager {
297
302
  }
298
303
  /* @dev-only:end */
299
304
 
305
+ // 0. Check honeypot fields first (bot detection)
306
+ if (this._isHoneypotFilled()) {
307
+ /* @dev-only:start */
308
+ {
309
+ console.log('[Form-manager] Honeypot triggered - rejecting submission');
310
+ }
311
+ /* @dev-only:end */
312
+
313
+ // Emit honeypot event for tracking
314
+ this._emit('honeypot', { data });
315
+
316
+ this.showError('Something went wrong. Please try again.');
317
+ return false;
318
+ }
319
+
300
320
  // Create setError helper for custom validation
301
321
  const setError = (fieldName, message) => {
302
322
  this._fieldErrors[fieldName] = message;
@@ -712,30 +732,68 @@ export class FormManager {
712
732
 
713
733
  /**
714
734
  * Collect form data as plain object (supports dot notation for nested fields)
735
+ * Respects inputGroup filter when set - only includes fields matching the group
715
736
  */
716
737
  getData() {
717
- const formData = new FormData(this.$form);
718
738
  const data = {};
719
739
 
720
- // Count checkboxes per name to detect groups vs single
740
+ // Get all form fields
741
+ const $fields = this.$form.querySelectorAll('input, select, textarea');
742
+
743
+ // Count checkboxes per name to detect groups vs single (only for fields in group)
721
744
  const checkboxCounts = {};
722
- this.$form.querySelectorAll('input[type="checkbox"]').forEach(($cb) => {
723
- checkboxCounts[$cb.name] = (checkboxCounts[$cb.name] || 0) + 1;
745
+ $fields.forEach(($field) => {
746
+ if ($field.type === 'checkbox' && this._isFieldInGroup($field)) {
747
+ checkboxCounts[$field.name] = (checkboxCounts[$field.name] || 0) + 1;
748
+ }
724
749
  });
725
750
 
726
- for (const [key, value] of formData.entries()) {
751
+ // Process non-checkbox fields
752
+ $fields.forEach(($field) => {
753
+ const name = $field.name;
754
+
755
+ // Skip fields without name
756
+ if (!name) {
757
+ return;
758
+ }
759
+
760
+ // Skip if field is not in current input group
761
+ if (!this._isFieldInGroup($field)) {
762
+ return;
763
+ }
764
+
765
+ // Skip honeypot fields (should never be in form data)
766
+ if ($field.matches(HONEYPOT_SELECTOR)) {
767
+ return;
768
+ }
769
+
727
770
  // Skip checkboxes - we handle them separately
728
- if (checkboxCounts[key]) {
729
- continue;
771
+ if ($field.type === 'checkbox') {
772
+ return;
730
773
  }
731
- this._setNested(data, key, value);
732
- }
774
+
775
+ // Skip radio buttons that aren't checked
776
+ if ($field.type === 'radio' && !$field.checked) {
777
+ return;
778
+ }
779
+
780
+ this._setNested(data, name, $field.value);
781
+ });
733
782
 
734
783
  // Handle checkboxes
735
784
  const processedGroups = new Set();
736
- this.$form.querySelectorAll('input[type="checkbox"]').forEach(($cb) => {
785
+ $fields.forEach(($cb) => {
786
+ if ($cb.type !== 'checkbox') {
787
+ return;
788
+ }
789
+
737
790
  const name = $cb.name;
738
791
 
792
+ // Skip if field is not in current input group
793
+ if (!this._isFieldInGroup($cb)) {
794
+ return;
795
+ }
796
+
739
797
  // Single checkbox: true/false
740
798
  if (checkboxCounts[name] === 1) {
741
799
  this._setNested(data, name, $cb.checked);
@@ -750,7 +808,10 @@ export class FormManager {
750
808
 
751
809
  const values = {};
752
810
  this.$form.querySelectorAll(`input[type="checkbox"][name="${name}"]`).forEach(($groupCb) => {
753
- values[$groupCb.value] = $groupCb.checked;
811
+ // Only include checkboxes that are in the group
812
+ if (this._isFieldInGroup($groupCb)) {
813
+ values[$groupCb.value] = $groupCb.checked;
814
+ }
754
815
  });
755
816
  this._setNested(data, name, values);
756
817
  });
@@ -806,6 +867,83 @@ export class FormManager {
806
867
  return this._isDirty;
807
868
  }
808
869
 
870
+ /**
871
+ * Set the input group filter for getData()
872
+ * When set, getData() only returns fields matching the group (via data-input-group attribute)
873
+ * Fields without data-input-group or with empty value are considered "global" and always included
874
+ *
875
+ * @param {string|string[]|null} group - Group name(s) to filter by (e.g., 'url', ['url', 'wifi']), or null to disable filtering
876
+ * @returns {FormManager} - Returns this for chaining
877
+ */
878
+ setInputGroup(group) {
879
+ // Normalize to array or null
880
+ if (group === null || group === undefined || group === '') {
881
+ this.config.inputGroup = null;
882
+ } else if (Array.isArray(group)) {
883
+ this.config.inputGroup = group.map((g) => g.toLowerCase());
884
+ } else {
885
+ this.config.inputGroup = [group.toLowerCase()];
886
+ }
887
+
888
+ /* @dev-only:start */
889
+ {
890
+ console.log('[Form-manager] setInputGroup:', this.config.inputGroup);
891
+ }
892
+ /* @dev-only:end */
893
+
894
+ return this;
895
+ }
896
+
897
+ /**
898
+ * Get the current input group filter
899
+ * @returns {string[]|null}
900
+ */
901
+ getInputGroup() {
902
+ return this.config.inputGroup;
903
+ }
904
+
905
+ /**
906
+ * Check if any honeypot field has been filled (bot detection)
907
+ * Honeypot fields are hidden from users but bots fill them automatically
908
+ * @returns {boolean} - true if a honeypot field has a value (bot detected)
909
+ */
910
+ _isHoneypotFilled() {
911
+ const $honeypots = this.$form.querySelectorAll(HONEYPOT_SELECTOR);
912
+
913
+ for (const $field of $honeypots) {
914
+ if ($field.value && $field.value.trim() !== '') {
915
+ return true;
916
+ }
917
+ }
918
+
919
+ return false;
920
+ }
921
+
922
+ /**
923
+ * Check if a field should be included based on input group filter
924
+ * @param {HTMLElement} $field - The field element to check
925
+ * @returns {boolean}
926
+ */
927
+ _isFieldInGroup($field) {
928
+ const allowedGroups = this.config.inputGroup;
929
+
930
+ // No filter set - include all fields
931
+ if (!allowedGroups) {
932
+ return true;
933
+ }
934
+
935
+ // Get field's group attribute
936
+ const fieldGroup = $field.getAttribute('data-input-group');
937
+
938
+ // No group attribute or empty = global field, always include
939
+ if (!fieldGroup || fieldGroup.trim() === '') {
940
+ return true;
941
+ }
942
+
943
+ // Check if field's group is in allowed groups
944
+ return allowedGroups.includes(fieldGroup.toLowerCase());
945
+ }
946
+
809
947
  /**
810
948
  * Set form data from a nested object (supports dot notation field names)
811
949
  */
@@ -32,18 +32,16 @@ function setupForm() {
32
32
  resetOnSuccess: true,
33
33
  });
34
34
 
35
+ // Track honeypot triggers (spam detection)
36
+ formManager.on('honeypot', () => {
37
+ trackContactSpam();
38
+ });
39
+
35
40
  formManager.on('submit', async ({ data }) => {
36
41
  const slapformId = webManager.config.brand.contact['slapform-form-id'];
37
42
 
38
43
  console.log('Contact form submission:', data);
39
44
 
40
- // Check honeypot fields (anti-spam)
41
- if (data.url_check || data.slap_honey) {
42
- console.warn('Honeypot field filled - potential spam');
43
- trackContactSpam();
44
- return;
45
- }
46
-
47
45
  // Check if slapformId is missing
48
46
  if (!slapformId) {
49
47
  webManager.sentry().captureException(new Error('Contact form is not configured - missing slapform ID'));
@@ -21,6 +21,7 @@ export default (Manager) => {
21
21
  initTestFormValidation();
22
22
  initTestFormContact();
23
23
  initTestFormManual();
24
+ initTestFormGroups();
24
25
 
25
26
  // Resolve after initialization
26
27
  return resolve();
@@ -192,3 +193,55 @@ function initTestFormManual() {
192
193
  formManager.ready();
193
194
  }, 2000);
194
195
  }
196
+
197
+ // Test 5: Input Groups
198
+ function initTestFormGroups() {
199
+ const formManager = new FormManager('#test-form-groups');
200
+ const $status = document.getElementById('groups-status');
201
+ const $filter = document.getElementById('groups-filter');
202
+ const $output = document.getElementById('groups-output');
203
+ const $filterBtns = document.querySelectorAll('.groups-filter-btn');
204
+
205
+ formManager.on('statechange', ({ state }) => {
206
+ $status.textContent = `Status: ${state}`;
207
+ });
208
+
209
+ // Filter button click handler
210
+ $filterBtns.forEach(($btn) => {
211
+ $btn.addEventListener('click', () => {
212
+ // Update active state
213
+ $filterBtns.forEach(($b) => $b.classList.remove('active'));
214
+ $btn.classList.add('active');
215
+
216
+ // Set the input group filter (parse data-group as JSON array or single string)
217
+ const groupAttr = $btn.dataset.group;
218
+ let group = null;
219
+ if (groupAttr) {
220
+ try {
221
+ group = JSON.parse(groupAttr);
222
+ } catch {
223
+ group = groupAttr; // Single string value
224
+ }
225
+ }
226
+ formManager.setInputGroup(group);
227
+
228
+ // Update filter display
229
+ const currentGroup = formManager.getInputGroup();
230
+ $filter.textContent = currentGroup
231
+ ? `Filter: ${JSON.stringify(currentGroup)}`
232
+ : 'Filter: none (all fields)';
233
+
234
+ console.log('[Test 5] Input group set to:', currentGroup);
235
+ });
236
+ });
237
+
238
+ formManager.on('submit', async ({ data }) => {
239
+ console.log('[Test 5] getData() result:', data);
240
+
241
+ // Show the filtered data
242
+ $output.textContent = JSON.stringify(data, null, 2);
243
+
244
+ // Don't actually submit - just show the data
245
+ formManager.showSuccess('getData() returned ' + Object.keys(data).length + ' top-level keys');
246
+ });
247
+ }
@@ -70,7 +70,7 @@
70
70
  <span class="text-muted">
71
71
 
72
72
  </span>
73
- <small class="ms-2 text-muted"><strong class="text-body">10k+</strong> happy subscribers</small>
73
+ <small class="ms-2 text-muted"><strong class="text-body">Join 10k+</strong> happy subscribers!</small>
74
74
  </div>
75
75
 
76
76
  <!-- Trust indicators -->
@@ -203,9 +203,9 @@ faqs:
203
203
  </div>
204
204
  </div>
205
205
 
206
- <!-- Honeypot field -->
206
+ <!-- Honeypot field (bot detection) -->
207
207
  <div class="form-group mb-3" style="display: none;" aria-hidden="true">
208
- <input type="text" class="form-control" id="url_check" name="url_check" placeholder="URL Check" tabindex="-1" autocomplete="off">
208
+ <input type="text" class="form-control" name="honey" tabindex="-1" autocomplete="off">
209
209
  </div>
210
210
 
211
211
  <div class="mb-4">
@@ -238,11 +238,6 @@ faqs:
238
238
  <div class="invalid-feedback"></div>
239
239
  </div>
240
240
 
241
- <!-- Honeypot field -->
242
- <div class="form-group mb-3 position-absolute" style="opacity: 0; height: 0;" aria-hidden="true">
243
- <input type="text" class="form-control" id="slap_honey" name="slap_honey" placeholder="Website" tabindex="-1" autocomplete="off">
244
- </div>
245
-
246
241
  <div class="mb-4">
247
242
  <label for="message" class="form-label fw-semibold">Message <span class="text-danger">*</span></label>
248
243
  <textarea class="form-control form-control-lg" id="message" name="message" rows="3"
@@ -36,7 +36,7 @@ hero:
36
36
  href: "/dashboard"
37
37
  class: "btn-light"
38
38
  secondary_button:
39
- text: "Explore Solutions"
39
+ text: "See Pricing"
40
40
  icon: "book-open"
41
41
  href: "/pricing"
42
42
  class: "btn-outline-light"
@@ -77,6 +77,12 @@ sitemap: false
77
77
  <input type="checkbox" class="form-check-input" id="main-subscribe" name="settings.subscribe" disabled>
78
78
  <label class="form-check-label" for="main-subscribe">settings.subscribe (single checkbox)</label>
79
79
  </div>
80
+ <!-- Honeypot field for bot detection (fill to trigger rejection) -->
81
+ <div class="mb-3">
82
+ <label for="main-honey" class="form-label">honey <span class="badge bg-warning text-dark">Honeypot</span></label>
83
+ <input type="text" class="form-control" id="main-honey" name="honey" data-honey placeholder="Fill to trigger bot rejection" autocomplete="off" tabindex="-1" disabled>
84
+ <small class="text-muted">Hidden in production; fill here to test rejection</small>
85
+ </div>
80
86
  </div>
81
87
  </div>
82
88
  <div class="d-flex gap-2">
@@ -177,5 +183,78 @@ sitemap: false
177
183
  </div>
178
184
  </div>
179
185
 
186
+ <!-- Test 5: Input Groups -->
187
+ <div class="col-lg-8">
188
+ <div class="card">
189
+ <div class="card-header">
190
+ <h5 class="mb-0">Test 5: Input Groups</h5>
191
+ <small class="text-muted">setInputGroup() filters getData() - fields without data-input-group are global</small>
192
+ </div>
193
+ <div class="card-body">
194
+ <form id="test-form-groups">
195
+ <div class="row">
196
+ <!-- Global settings (no data-input-group = always included) -->
197
+ <div class="col-md-4">
198
+ <h6 class="text-muted mb-3">Global Settings <span class="badge bg-secondary">Always included</span></h6>
199
+ <div class="mb-3">
200
+ <label for="groups-name" class="form-label">settings.name</label>
201
+ <input type="text" class="form-control" id="groups-name" name="settings.name" value="My Project" disabled>
202
+ </div>
203
+ <div class="mb-3">
204
+ <label for="groups-theme" class="form-label">settings.theme</label>
205
+ <select class="form-select" id="groups-theme" name="settings.theme" disabled>
206
+ <option value="light">Light</option>
207
+ <option value="dark" selected>Dark</option>
208
+ </select>
209
+ </div>
210
+ </div>
211
+
212
+ <!-- Group A fields -->
213
+ <div class="col-md-4">
214
+ <h6 class="text-primary mb-3">Group A <span class="badge bg-primary">data-input-group="a"</span></h6>
215
+ <div class="mb-3">
216
+ <label for="groups-a-url" class="form-label">options.url</label>
217
+ <input type="url" class="form-control" id="groups-a-url" name="options.url" data-input-group="a" value="https://example.com" disabled>
218
+ </div>
219
+ <div class="mb-3">
220
+ <label for="groups-a-title" class="form-label">options.title</label>
221
+ <input type="text" class="form-control" id="groups-a-title" name="options.title" data-input-group="a" value="My Link" disabled>
222
+ </div>
223
+ </div>
224
+
225
+ <!-- Group B fields -->
226
+ <div class="col-md-4">
227
+ <h6 class="text-success mb-3">Group B <span class="badge bg-success">data-input-group="b"</span></h6>
228
+ <div class="mb-3">
229
+ <label for="groups-b-ssid" class="form-label">options.ssid</label>
230
+ <input type="text" class="form-control" id="groups-b-ssid" name="options.ssid" data-input-group="b" value="MyWiFi" disabled>
231
+ </div>
232
+ <div class="mb-3">
233
+ <label for="groups-b-password" class="form-label">options.password</label>
234
+ <input type="text" class="form-control" id="groups-b-password" name="options.password" data-input-group="b" value="secret123" disabled>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ <div class="d-flex gap-2 flex-wrap align-items-center">
240
+ <span class="text-muted me-2">Select group:</span>
241
+ <button type="button" class="btn btn-outline-secondary btn-sm groups-filter-btn active" data-group="">All (no filter)</button>
242
+ <button type="button" class="btn btn-outline-primary btn-sm groups-filter-btn" data-group="a">Group A</button>
243
+ <button type="button" class="btn btn-outline-success btn-sm groups-filter-btn" data-group="b">Group B</button>
244
+ <button type="button" class="btn btn-outline-info btn-sm groups-filter-btn" data-group='["a", "b"]'>Both (a, b)</button>
245
+ <button type="submit" class="btn btn-primary btn-sm ms-auto" disabled>Get Data</button>
246
+ </div>
247
+ </form>
248
+ <div class="mt-3 d-flex gap-3">
249
+ <small class="text-muted" id="groups-status">Status: ready</small>
250
+ <small class="text-muted" id="groups-filter">Filter: none (all fields)</small>
251
+ </div>
252
+ <div class="mt-3">
253
+ <pre id="groups-output" class="bg-body-secondary p-2 rounded small" style="max-height: 200px; overflow: auto;">Click "Get Data" to see filtered results...</pre>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </div>
258
+
180
259
  </div>
181
260
  </div>
@@ -47,7 +47,7 @@ By vising {{ brand }}, you agree to comply.
47
47
  ## This is an input
48
48
  <div class="form-group">
49
49
  <label for="test-email" class="visually-hidden">Your email</label>
50
- <input type="email" id="test-email" name="slap_honey" class="form-control" placeholder="Your Email">
50
+ <input type="email" id="test-email" name="email" class="form-control" placeholder="Your Email">
51
51
  </div>
52
52
 
53
53
  ## This button has a title
@@ -3154,3 +3154,87 @@
3154
3154
  [debug] [2025-12-10T06:23:42.224Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3155
3155
  [debug] [2025-12-10T06:23:42.225Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3156
3156
  [debug] [2025-12-10T06:23:42.226Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3157
+ [debug] [2025-12-10T10:33:22.717Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3158
+ [debug] [2025-12-10T10:33:22.717Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3159
+ [debug] [2025-12-10T10:33:22.719Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3160
+ [debug] [2025-12-10T10:33:22.719Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3161
+ [debug] [2025-12-10T10:33:22.719Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3162
+ [debug] [2025-12-10T10:33:22.728Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3163
+ [debug] [2025-12-10T10:33:22.728Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3164
+ [debug] [2025-12-10T10:33:22.719Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3165
+ [debug] [2025-12-10T10:33:22.719Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3166
+ [debug] [2025-12-10T10:33:22.719Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3167
+ [debug] [2025-12-10T10:33:22.728Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3168
+ [debug] [2025-12-10T10:33:22.728Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3169
+ [debug] [2025-12-10T10:33:22.814Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3170
+ [debug] [2025-12-10T10:33:22.816Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3171
+ [debug] [2025-12-10T10:33:22.814Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3172
+ [debug] [2025-12-10T10:33:22.815Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3173
+ [debug] [2025-12-10T10:33:22.815Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3174
+ [debug] [2025-12-10T10:33:22.817Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3175
+ [debug] [2025-12-10T10:33:22.817Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3176
+ [debug] [2025-12-10T10:33:22.817Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3177
+ [debug] [2025-12-10T10:33:22.817Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3178
+ [debug] [2025-12-10T10:33:22.816Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3179
+ [debug] [2025-12-10T10:33:22.817Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3180
+ [debug] [2025-12-10T10:33:22.817Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3181
+ [debug] [2025-12-10T10:33:22.818Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3182
+ [debug] [2025-12-10T10:33:22.818Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3183
+ [debug] [2025-12-10T10:33:22.819Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3184
+ [debug] [2025-12-10T10:33:22.819Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3185
+ [debug] [2025-12-11T03:09:30.498Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3186
+ [debug] [2025-12-11T03:09:30.500Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3187
+ [debug] [2025-12-11T03:09:30.500Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3188
+ [debug] [2025-12-11T03:09:30.500Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3189
+ [debug] [2025-12-11T03:09:30.509Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3190
+ [debug] [2025-12-11T03:09:30.509Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3191
+ [debug] [2025-12-11T03:09:30.596Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3192
+ [debug] [2025-12-11T03:09:30.596Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3193
+ [debug] [2025-12-11T03:09:30.596Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3194
+ [debug] [2025-12-11T03:09:30.597Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3195
+ [debug] [2025-12-11T03:09:30.598Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3196
+ [debug] [2025-12-11T03:09:30.599Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3197
+ [debug] [2025-12-11T03:09:30.599Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3198
+ [debug] [2025-12-11T03:09:30.599Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3199
+ [debug] [2025-12-11T03:31:03.777Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3200
+ [debug] [2025-12-11T03:31:03.777Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3201
+ [debug] [2025-12-11T03:31:03.779Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3202
+ [debug] [2025-12-11T03:31:03.779Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3203
+ [debug] [2025-12-11T03:31:03.779Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3204
+ [debug] [2025-12-11T03:31:03.779Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3205
+ [debug] [2025-12-11T03:31:03.788Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3206
+ [debug] [2025-12-11T03:31:03.788Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3207
+ [debug] [2025-12-11T03:31:03.779Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3208
+ [debug] [2025-12-11T03:31:03.779Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3209
+ [debug] [2025-12-11T03:31:03.779Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3210
+ [debug] [2025-12-11T03:31:03.788Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3211
+ [debug] [2025-12-11T03:31:03.789Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3212
+ [debug] [2025-12-11T03:31:03.781Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3213
+ [debug] [2025-12-11T03:31:03.781Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3214
+ [debug] [2025-12-11T03:31:03.781Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3215
+ [debug] [2025-12-11T03:31:03.792Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3216
+ [debug] [2025-12-11T03:31:03.793Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3217
+ [debug] [2025-12-11T03:31:03.891Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3218
+ [debug] [2025-12-11T03:31:03.892Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3219
+ [debug] [2025-12-11T03:31:03.892Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3220
+ [debug] [2025-12-11T03:31:03.893Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3221
+ [debug] [2025-12-11T03:31:03.894Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3222
+ [debug] [2025-12-11T03:31:03.894Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3223
+ [debug] [2025-12-11T03:31:03.895Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3224
+ [debug] [2025-12-11T03:31:03.895Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3225
+ [debug] [2025-12-11T03:31:03.900Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3226
+ [debug] [2025-12-11T03:31:03.901Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3227
+ [debug] [2025-12-11T03:31:03.900Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3228
+ [debug] [2025-12-11T03:31:03.901Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3229
+ [debug] [2025-12-11T03:31:03.901Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3230
+ [debug] [2025-12-11T03:31:03.903Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3231
+ [debug] [2025-12-11T03:31:03.903Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3232
+ [debug] [2025-12-11T03:31:03.904Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3233
+ [debug] [2025-12-11T03:31:03.904Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3234
+ [debug] [2025-12-11T03:31:03.901Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3235
+ [debug] [2025-12-11T03:31:03.902Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3236
+ [debug] [2025-12-11T03:31:03.902Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3237
+ [debug] [2025-12-11T03:31:03.904Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3238
+ [debug] [2025-12-11T03:31:03.904Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
3239
+ [debug] [2025-12-11T03:31:03.904Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
3240
+ [debug] [2025-12-11T03:31:03.905Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "0.0.150",
3
+ "version": "0.0.151",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {