drf-to-mkdoc 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

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.

Potentially problematic release.


This version of drf-to-mkdoc might be problematic. Click here for more details.

Files changed (59) hide show
  1. drf_to_mkdoc/conf/defaults.py +1 -0
  2. drf_to_mkdoc/conf/settings.py +0 -2
  3. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/form-manager.js +172 -0
  4. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/main.js +22 -0
  5. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/modal.js +79 -0
  6. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/request-executor.js +111 -0
  7. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/suggestions.js +216 -0
  8. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/tabs.js +34 -0
  9. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/buttons.css +71 -0
  10. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/fab.css +47 -0
  11. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/form.css +124 -0
  12. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/key-value.css +161 -0
  13. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/main.css +57 -0
  14. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/modal.css +112 -0
  15. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/response.css +158 -0
  16. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/tabs.css +62 -0
  17. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/variables.css +38 -0
  18. drf_to_mkdoc/templates/endpoints/detail/base.html +35 -0
  19. drf_to_mkdoc/templates/endpoints/detail/path_parameters.html +8 -0
  20. drf_to_mkdoc/templates/endpoints/detail/query_parameters.html +36 -0
  21. drf_to_mkdoc/templates/endpoints/detail/request_body.html +10 -0
  22. drf_to_mkdoc/templates/endpoints/detail/responses.html +18 -0
  23. drf_to_mkdoc/templates/endpoints/list/base.html +23 -0
  24. drf_to_mkdoc/templates/endpoints/list/endpoint_card.html +18 -0
  25. drf_to_mkdoc/templates/endpoints/list/filter_section.html +16 -0
  26. drf_to_mkdoc/templates/endpoints/list/filters/app.html +8 -0
  27. drf_to_mkdoc/templates/endpoints/list/filters/method.html +12 -0
  28. drf_to_mkdoc/templates/endpoints/list/filters/path.html +5 -0
  29. drf_to_mkdoc/templates/endpoints/list/filters/search.html +9 -0
  30. drf_to_mkdoc/templates/model_detail/base.html +34 -0
  31. drf_to_mkdoc/templates/model_detail/choices.html +12 -0
  32. drf_to_mkdoc/templates/model_detail/fields.html +11 -0
  33. drf_to_mkdoc/templates/model_detail/meta.html +6 -0
  34. drf_to_mkdoc/templates/model_detail/methods.html +9 -0
  35. drf_to_mkdoc/templates/model_detail/relationships.html +8 -0
  36. drf_to_mkdoc/templates/models_index.html +24 -0
  37. drf_to_mkdoc/templates/try-out/fab.html +4 -0
  38. drf_to_mkdoc/templates/try-out/form.html +113 -0
  39. drf_to_mkdoc/templates/try-out/main.html +4 -0
  40. drf_to_mkdoc/templates/try-out/modal.html +14 -0
  41. drf_to_mkdoc/templates/try-out/response-modal.html +20 -0
  42. drf_to_mkdoc/templatetags/custom_filters.py +148 -0
  43. drf_to_mkdoc/utils/commons/schema_utils.py +5 -14
  44. drf_to_mkdoc/utils/endpoint_detail_generator.py +201 -171
  45. drf_to_mkdoc/utils/endpoint_list_generator.py +58 -193
  46. drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +0 -15
  47. drf_to_mkdoc/utils/model_detail_generator.py +22 -202
  48. drf_to_mkdoc/utils/model_list_generator.py +26 -44
  49. drf_to_mkdoc/utils/schema.py +1 -1
  50. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/METADATA +1 -1
  51. drf_to_mkdoc-0.2.3.dist-info/RECORD +103 -0
  52. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js +0 -879
  53. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out-sidebar.css +0 -728
  54. drf_to_mkdoc/utils/md_generators/__init__.py +0 -0
  55. drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +0 -72
  56. drf_to_mkdoc-0.2.1.dist-info/RECORD +0 -67
  57. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/WHEEL +0 -0
  58. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/licenses/LICENSE +0 -0
  59. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ DEFAULTS = {
7
7
  "CUSTOM_SCHEMA_FILE": "docs/configs/custom_schema.json", # Path to custom schema file
8
8
  "PATH_PARAM_SUBSTITUTE_FUNCTION": None,
9
9
  "PATH_PARAM_SUBSTITUTE_MAPPING": {},
10
+ "FIELD_GENERATORS": {},
10
11
  # AI documentation settings
11
12
  "ENABLE_AI_DOCS": False,
12
13
  "AI_CONFIG_DIR_NAME": "ai_code", # Directory name for AI-generated code files
@@ -138,8 +138,6 @@ class DRFToMkDocSettings:
138
138
  self._validate_range(key, value)
139
139
  self._validate_dir(key, value)
140
140
 
141
- if value is None:
142
- raise AttributeError(f"Invalid DRF_TO_MKDOC setting: '{key}'")
143
141
  return value
144
142
 
145
143
  def __getattr__(self, key):
@@ -0,0 +1,172 @@
1
+ // Form management functionality
2
+ const FormManager = {
3
+ addQueryParam: function() {
4
+ const container = document.getElementById('queryParams');
5
+ if (!container) return;
6
+
7
+ const kvItem = this.createKvItem('Parameter name', 'Parameter value', true);
8
+ container.appendChild(kvItem);
9
+
10
+ // Focus on the first input
11
+ const firstInput = kvItem.querySelector('input');
12
+ if (firstInput) {
13
+ firstInput.focus();
14
+ }
15
+ },
16
+
17
+ addHeader: function() {
18
+ const container = document.getElementById('requestHeaders');
19
+ if (!container) return;
20
+
21
+ const kvItem = this.createKvItem('Header name', 'Header value', true);
22
+ container.appendChild(kvItem);
23
+
24
+ // Focus on the first input
25
+ const firstInput = kvItem.querySelector('input');
26
+ if (firstInput) {
27
+ firstInput.focus();
28
+ }
29
+ },
30
+
31
+ createKvItem: function(namePlaceholder, valuePlaceholder, removable = true) {
32
+ const kvItem = document.createElement('div');
33
+ kvItem.className = 'kv-item';
34
+
35
+ const nameInput = document.createElement('input');
36
+ nameInput.type = 'text';
37
+ nameInput.placeholder = namePlaceholder;
38
+
39
+ const valueInput = document.createElement('input');
40
+ valueInput.type = 'text';
41
+ valueInput.placeholder = valuePlaceholder;
42
+
43
+ kvItem.appendChild(nameInput);
44
+ kvItem.appendChild(valueInput);
45
+
46
+ if (removable) {
47
+ const removeBtn = document.createElement('button');
48
+ removeBtn.className = 'remove-btn';
49
+ removeBtn.textContent = '✕';
50
+ removeBtn.addEventListener('click', () => this.removeKvItem(removeBtn));
51
+ kvItem.appendChild(removeBtn);
52
+ }
53
+
54
+ return kvItem;
55
+ },
56
+
57
+ removeKvItem: function(button) {
58
+ if (button && button.parentElement) {
59
+ button.parentElement.remove();
60
+ }
61
+ },
62
+
63
+ validateRequiredParams: function() {
64
+ const requiredInputs = document.querySelectorAll('#pathParams input[required]');
65
+ const errors = [];
66
+
67
+ requiredInputs.forEach(input => {
68
+ const errorElement = input.parentElement.querySelector('.error-message');
69
+
70
+ if (!input.value.trim()) {
71
+ const paramName = input.getAttribute('data-param');
72
+ errors.push(paramName);
73
+ input.classList.add('error');
74
+
75
+ if (errorElement) {
76
+ errorElement.textContent = `${paramName} is required`;
77
+ errorElement.classList.add('show');
78
+ }
79
+
80
+ // Remove error on input
81
+ input.addEventListener('input', () => {
82
+ input.classList.remove('error');
83
+ if (errorElement) {
84
+ errorElement.classList.remove('show');
85
+ }
86
+ }, { once: true });
87
+ } else {
88
+ input.classList.remove('error');
89
+ if (errorElement) {
90
+ errorElement.classList.remove('show');
91
+ }
92
+ }
93
+ });
94
+
95
+ return errors;
96
+ },
97
+
98
+ addSuggestion: function(input, suggestion) {
99
+ input.value = suggestion;
100
+ input.focus();
101
+ },
102
+
103
+ buildRequestUrl: function() {
104
+ const baseUrl = document.getElementById('baseUrl').value.trim();
105
+ const pathDisplay = document.querySelector('.path-display').textContent.trim();
106
+
107
+ let url = baseUrl + pathDisplay;
108
+
109
+ // Replace path parameters
110
+ const pathParams = document.querySelectorAll('#pathParams input');
111
+ pathParams.forEach(input => {
112
+ const paramName = input.getAttribute('data-param');
113
+ const paramValue = input.value.trim();
114
+ if (paramName && paramValue) {
115
+ url = url.replace(`{${paramName}}`, encodeURIComponent(paramValue));
116
+ }
117
+ });
118
+
119
+ // Add query parameters
120
+ const queryParams = [];
121
+ const queryInputs = document.querySelectorAll('#queryParams .kv-item');
122
+ queryInputs.forEach(item => {
123
+ const inputs = item.querySelectorAll('input');
124
+ if (inputs.length === 2) {
125
+ const name = inputs[0].value.trim();
126
+ const value = inputs[1].value.trim();
127
+ if (name && value) {
128
+ queryParams.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
129
+ }
130
+ }
131
+ });
132
+
133
+ if (queryParams.length > 0) {
134
+ url += '?' + queryParams.join('&');
135
+ }
136
+
137
+ return url;
138
+ },
139
+
140
+ getRequestHeaders: function() {
141
+ const headers = {};
142
+ const headerInputs = document.querySelectorAll('#requestHeaders .kv-item');
143
+
144
+ headerInputs.forEach(item => {
145
+ const inputs = item.querySelectorAll('input');
146
+ if (inputs.length === 2) {
147
+ const name = inputs[0].value.trim();
148
+ const value = inputs[1].value.trim();
149
+ if (name && value) {
150
+ headers[name] = value;
151
+ }
152
+ }
153
+ });
154
+
155
+ return headers;
156
+ },
157
+
158
+ getRequestBody: function() {
159
+ const bodyTextarea = document.getElementById('requestBody');
160
+ if (bodyTextarea && bodyTextarea.value.trim()) {
161
+ try {
162
+ return JSON.parse(bodyTextarea.value);
163
+ } catch (e) {
164
+ return bodyTextarea.value;
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+ };
170
+
171
+ // Export for global access
172
+ window.FormManager = FormManager;
@@ -0,0 +1,22 @@
1
+ // Main try-out functionality - combines all components
2
+ document.addEventListener('DOMContentLoaded', function() {
3
+ // Initialize tabs
4
+ TabManager.init();
5
+
6
+ // Initialize suggestions if available
7
+ if (window.TryOutSuggestions) {
8
+ TryOutSuggestions.init();
9
+ }
10
+ });
11
+
12
+ // Legacy compatibility - maintain old interface
13
+ window.TryOutSidebar = {
14
+ openTryOut: () => ModalManager.openTryOut(),
15
+ closeTryOut: () => ModalManager.closeTryOut(),
16
+ closeResponseModal: () => ModalManager.closeResponseModal(),
17
+ showResponseModal: (status, responseText, responseTime) => ModalManager.showResponseModal(status, responseText, responseTime),
18
+ addQueryParam: () => FormManager.addQueryParam(),
19
+ addHeader: () => FormManager.addHeader(),
20
+ removeKvItem: (button) => FormManager.removeKvItem(button),
21
+ validateRequiredParams: () => FormManager.validateRequiredParams()
22
+ };
@@ -0,0 +1,79 @@
1
+ // Modal management functionality
2
+ const ModalManager = {
3
+ openTryOut: function() {
4
+ const modal = document.getElementById('tryOutModal');
5
+ if (modal) {
6
+ modal.classList.add('show');
7
+ modal.style.display = 'flex';
8
+ document.body.classList.add('modal-open');
9
+
10
+ // Focus management
11
+ const firstInput = modal.querySelector('input, button');
12
+ if (firstInput) {
13
+ firstInput.focus();
14
+ }
15
+ }
16
+ },
17
+
18
+ closeTryOut: function() {
19
+ const modal = document.getElementById('tryOutModal');
20
+ if (modal) {
21
+ modal.classList.remove('show');
22
+ modal.style.display = 'none';
23
+ document.body.classList.remove('modal-open');
24
+ }
25
+ },
26
+
27
+ openResponseModal: function() {
28
+ const modal = document.getElementById('responseModal');
29
+ if (modal) {
30
+ modal.classList.add('show');
31
+ modal.style.display = 'flex';
32
+ }
33
+ },
34
+
35
+ closeResponseModal: function() {
36
+ const modal = document.getElementById('responseModal');
37
+ if (modal) {
38
+ modal.classList.remove('show');
39
+ modal.style.display = 'none';
40
+ }
41
+ },
42
+
43
+ showResponseModal: function(status, responseText, responseTime) {
44
+ const modal = document.getElementById('responseModal');
45
+ const statusBadge = document.getElementById('modalStatusBadge');
46
+ const responseBody = document.getElementById('modalResponseBody');
47
+ const responseInfo = document.getElementById('responseInfo');
48
+
49
+ if (modal && statusBadge && responseBody) {
50
+ statusBadge.textContent = String(status);
51
+ const code = Number(status);
52
+ statusBadge.className = 'status-badge' + (Number.isFinite(code) ? ` status-${Math.floor(code/100)*100}` : '');
53
+
54
+ try {
55
+ const jsonResponse = JSON.parse(responseText);
56
+ responseBody.textContent = JSON.stringify(jsonResponse, null, 2);
57
+ } catch (e) {
58
+ responseBody.textContent = responseText;
59
+ }
60
+
61
+ if (responseInfo && responseTime) {
62
+ responseInfo.textContent = `Response time: ${responseTime}ms`;
63
+ }
64
+
65
+ this.openResponseModal();
66
+ }
67
+ }
68
+ };
69
+
70
+ // Keyboard navigation
71
+ document.addEventListener('keydown', function(e) {
72
+ if (e.key === 'Escape') {
73
+ ModalManager.closeTryOut();
74
+ ModalManager.closeResponseModal();
75
+ }
76
+ });
77
+
78
+ // Export for global access
79
+ window.ModalManager = ModalManager;
@@ -0,0 +1,111 @@
1
+ // Request execution functionality
2
+ const RequestExecutor = {
3
+ async executeRequest() {
4
+ const executeBtn = document.getElementById('executeBtn');
5
+ if (!executeBtn) return;
6
+
7
+ // Validate required parameters
8
+ const emptyParams = FormManager.validateRequiredParams();
9
+ if (emptyParams.length > 0) {
10
+ // Switch to parameters tab if we're on a different tab
11
+ const activeTab = document.querySelector('.try-out-form .tab.active');
12
+ if (activeTab && activeTab.getAttribute('data-tab') !== 'parameters') {
13
+ const parametersTab = document.querySelector('.try-out-form .tab[data-tab="parameters"]');
14
+ if (parametersTab) {
15
+ TabManager.switchTab(parametersTab);
16
+ }
17
+ }
18
+
19
+ this.showValidationError(`Please fill in the required parameters: ${emptyParams.join(', ')}`);
20
+ return;
21
+ }
22
+
23
+ // Show loading state
24
+ this.setLoadingState(executeBtn, true);
25
+
26
+ try {
27
+ const startTime = Date.now();
28
+ const url = FormManager.buildRequestUrl();
29
+ const headers = FormManager.getRequestHeaders();
30
+ const body = FormManager.getRequestBody();
31
+ const method = document.querySelector('.try-out-form')?.getAttribute('data-method') || 'GET';
32
+
33
+ const requestOptions = {
34
+ method: method.toUpperCase(),
35
+ headers: headers
36
+ };
37
+
38
+ // Add body for non-GET requests
39
+ if (body && !['GET', 'HEAD'].includes(method.toUpperCase())) {
40
+ if (typeof body === 'string') {
41
+ requestOptions.body = body;
42
+ } else {
43
+ requestOptions.body = JSON.stringify(body);
44
+ if (!headers['Content-Type']) {
45
+ requestOptions.headers['Content-Type'] = 'application/json';
46
+ }
47
+ }
48
+ }
49
+
50
+ const response = await fetch(url, requestOptions);
51
+ const responseTime = Date.now() - startTime;
52
+ const responseText = await response.text();
53
+
54
+ ModalManager.showResponseModal(response.status, responseText, responseTime);
55
+
56
+ } catch (error) {
57
+ let errorMessage = error.message || 'Unknown error occurred';
58
+ ModalManager.showResponseModal('Error', errorMessage);
59
+ } finally {
60
+ this.setLoadingState(executeBtn, false);
61
+ }
62
+ },
63
+
64
+ setLoadingState(button, loading) {
65
+ button.disabled = loading;
66
+ button.innerHTML = '';
67
+
68
+ if (loading) {
69
+ const spinner = document.createElement('div');
70
+ spinner.className = 'spinner';
71
+ const text = document.createTextNode(' Sending...');
72
+ button.appendChild(spinner);
73
+ button.appendChild(text);
74
+ } else {
75
+ const playIcon = document.createElement('span');
76
+ playIcon.textContent = '▶';
77
+ const text = document.createTextNode(' Execute Request');
78
+ button.appendChild(playIcon);
79
+ button.appendChild(text);
80
+ }
81
+ },
82
+
83
+ showValidationError(message) {
84
+ // Create or update validation error display
85
+ let errorDiv = document.getElementById('validation-error');
86
+ if (!errorDiv) {
87
+ errorDiv = document.createElement('div');
88
+ errorDiv.id = 'validation-error';
89
+ errorDiv.className = 'error-message show';
90
+
91
+ const executeBtn = document.getElementById('executeBtn');
92
+ if (executeBtn) {
93
+ executeBtn.parentNode.insertBefore(errorDiv, executeBtn);
94
+ }
95
+ }
96
+
97
+ errorDiv.textContent = message;
98
+ errorDiv.classList.add('show');
99
+
100
+ // Auto-hide after 5 seconds
101
+ setTimeout(() => {
102
+ errorDiv.classList.remove('show');
103
+ }, 5000);
104
+ }
105
+ };
106
+
107
+ // Global function for onclick handlers
108
+ window.executeRequest = () => RequestExecutor.executeRequest();
109
+
110
+ // Export for global access
111
+ window.RequestExecutor = RequestExecutor;
@@ -0,0 +1,216 @@
1
+ // Query parameter suggestions functionality
2
+ const TryOutSuggestions = {
3
+ init: function() {
4
+ this.suggestions = this.getAvailableSuggestions();
5
+ this.setupAutocomplete();
6
+ },
7
+
8
+ setupAutocomplete: function() {
9
+ // Setup event listeners for all query parameter inputs
10
+ document.addEventListener('click', (e) => {
11
+ // Hide all suggestion dropdowns when clicking outside
12
+ if (!e.target.matches('#queryParams input')) {
13
+ this.hideAllSuggestions();
14
+ }
15
+ });
16
+
17
+ // Initial setup for existing inputs
18
+ this.setupExistingInputs();
19
+
20
+ // Setup for the add button to attach listeners to new inputs
21
+ const addBtn = document.querySelector('.add-btn');
22
+ if (addBtn) {
23
+ addBtn.addEventListener('click', () => {
24
+ // Wait for DOM to update
25
+ setTimeout(() => {
26
+ this.setupExistingInputs();
27
+ }, 10);
28
+ });
29
+ }
30
+ },
31
+
32
+ setupExistingInputs: function() {
33
+ // Find all parameter name inputs
34
+ const paramInputs = document.querySelectorAll('#queryParams .kv-item input:first-child');
35
+ paramInputs.forEach(input => {
36
+ // Skip if already initialized
37
+ if (input.dataset.autocompleteInitialized) return;
38
+
39
+ // Mark as initialized
40
+ input.dataset.autocompleteInitialized = 'true';
41
+
42
+ // Create suggestions container for this input
43
+ const suggestionsContainer = document.createElement('div');
44
+ suggestionsContainer.className = 'param-suggestions';
45
+ suggestionsContainer.id = 'suggestions-' + Math.random().toString(36).substr(2, 9);
46
+ input.parentNode.style.position = 'relative';
47
+ input.parentNode.appendChild(suggestionsContainer);
48
+
49
+ // Store reference to container
50
+ input.dataset.suggestionsContainer = suggestionsContainer.id;
51
+
52
+ // Add event listeners
53
+ input.addEventListener('focus', () => this.showSuggestions(input));
54
+ input.addEventListener('input', () => this.filterSuggestions(input));
55
+ input.addEventListener('keydown', (e) => this.handleKeyNavigation(e, input));
56
+ });
57
+ },
58
+
59
+ getAvailableSuggestions: function() {
60
+ const suggestions = [];
61
+
62
+ // Try to get query parameters from the page context
63
+ if (window.queryParametersData) {
64
+ const data = window.queryParametersData;
65
+
66
+ // Add filter fields
67
+ if (data.filter_fields && data.filter_fields.length > 0) {
68
+ suggestions.push(...data.filter_fields);
69
+ }
70
+
71
+ // Add search if available
72
+ if (data.search_fields && data.search_fields.length > 0) {
73
+ suggestions.push('search');
74
+ }
75
+
76
+ // Add ordering if available
77
+ if (data.ordering_fields && data.ordering_fields.length > 0) {
78
+ suggestions.push('ordering');
79
+ }
80
+
81
+ // Add pagination
82
+ if (data.pagination_fields && data.pagination_fields.length > 0) {
83
+ suggestions.push(...data.pagination_fields);
84
+ }
85
+ }
86
+
87
+ // Default common parameters
88
+ if (suggestions.length === 0) {
89
+ suggestions.push('search', 'ordering', 'page', 'page_size');
90
+ }
91
+
92
+ // Remove duplicates and return
93
+ return [...new Set(suggestions)];
94
+ },
95
+
96
+ showSuggestions: function(input) {
97
+ const container = document.getElementById(input.dataset.suggestionsContainer);
98
+ if (!container) return;
99
+
100
+ // Clear existing suggestions
101
+ container.innerHTML = '';
102
+
103
+ // Filter suggestions based on input value
104
+ const inputValue = input.value.toLowerCase();
105
+ const filteredSuggestions = this.suggestions.filter(suggestion =>
106
+ suggestion.toLowerCase().includes(inputValue)
107
+ );
108
+
109
+ if (filteredSuggestions.length === 0) {
110
+ container.classList.remove('show');
111
+ return;
112
+ }
113
+
114
+ // Add suggestions to container
115
+ filteredSuggestions.forEach(suggestion => {
116
+ const suggestionElement = document.createElement('div');
117
+ suggestionElement.className = 'param-suggestion';
118
+ suggestionElement.textContent = suggestion;
119
+ suggestionElement.addEventListener('click', (e) => {
120
+ e.stopPropagation();
121
+ this.selectSuggestion(input, suggestion);
122
+ });
123
+ container.appendChild(suggestionElement);
124
+ });
125
+
126
+ // Show suggestions
127
+ container.classList.add('show');
128
+ },
129
+
130
+ filterSuggestions: function(input) {
131
+ // Just re-show suggestions with current filter
132
+ this.showSuggestions(input);
133
+ },
134
+
135
+ hideAllSuggestions: function() {
136
+ document.querySelectorAll('.param-suggestions').forEach(container => {
137
+ container.classList.remove('show');
138
+ });
139
+ },
140
+
141
+ selectSuggestion: function(input, suggestion) {
142
+ // Set input value
143
+ input.value = suggestion;
144
+
145
+ // Hide suggestions
146
+ const container = document.getElementById(input.dataset.suggestionsContainer);
147
+ if (container) {
148
+ container.classList.remove('show');
149
+ }
150
+
151
+ // Focus on value input
152
+ const valueInput = input.nextElementSibling;
153
+ if (valueInput) {
154
+ valueInput.focus();
155
+ }
156
+ },
157
+
158
+ handleKeyNavigation: function(event, input) {
159
+ const container = document.getElementById(input.dataset.suggestionsContainer);
160
+ if (!container || !container.classList.contains('show')) return;
161
+
162
+ const suggestions = container.querySelectorAll('.param-suggestion');
163
+ if (suggestions.length === 0) return;
164
+
165
+ // Find currently selected suggestion
166
+ const selectedIndex = Array.from(suggestions).findIndex(el => el.classList.contains('selected'));
167
+
168
+ switch (event.key) {
169
+ case 'ArrowDown':
170
+ event.preventDefault();
171
+ this.navigateSuggestion(suggestions, selectedIndex, 1);
172
+ break;
173
+
174
+ case 'ArrowUp':
175
+ event.preventDefault();
176
+ this.navigateSuggestion(suggestions, selectedIndex, -1);
177
+ break;
178
+
179
+ case 'Enter':
180
+ event.preventDefault();
181
+ if (selectedIndex >= 0) {
182
+ this.selectSuggestion(input, suggestions[selectedIndex].textContent);
183
+ } else if (suggestions.length > 0) {
184
+ this.selectSuggestion(input, suggestions[0].textContent);
185
+ }
186
+ break;
187
+
188
+ case 'Escape':
189
+ event.preventDefault();
190
+ container.classList.remove('show');
191
+ break;
192
+ }
193
+ },
194
+
195
+ navigateSuggestion: function(suggestions, currentIndex, direction) {
196
+ // Remove current selection
197
+ if (currentIndex >= 0) {
198
+ suggestions[currentIndex].classList.remove('selected');
199
+ }
200
+
201
+ // Calculate new index
202
+ let newIndex;
203
+ if (currentIndex < 0) {
204
+ newIndex = direction > 0 ? 0 : suggestions.length - 1;
205
+ } else {
206
+ newIndex = (currentIndex + direction + suggestions.length) % suggestions.length;
207
+ }
208
+
209
+ // Select new suggestion
210
+ suggestions[newIndex].classList.add('selected');
211
+ suggestions[newIndex].scrollIntoView({ block: 'nearest' });
212
+ }
213
+ };
214
+
215
+ // Export for global access
216
+ window.TryOutSuggestions = TryOutSuggestions;
@@ -0,0 +1,34 @@
1
+ // Tab management functionality
2
+ const TabManager = {
3
+ init: function() {
4
+ document.querySelectorAll('.try-out-form .tab').forEach(tab => {
5
+ tab.addEventListener('click', () => {
6
+ this.switchTab(tab);
7
+ });
8
+ });
9
+ },
10
+
11
+ switchTab: function(activeTab) {
12
+ // Remove active class from all tabs and contents
13
+ document.querySelectorAll('.try-out-form .tab').forEach(t => t.classList.remove('active'));
14
+ document.querySelectorAll('.try-out-form .tab-content').forEach(c => c.classList.remove('active'));
15
+
16
+ // Add active class to clicked tab
17
+ activeTab.classList.add('active');
18
+
19
+ // Show corresponding content
20
+ const tabName = activeTab.getAttribute('data-tab');
21
+ const content = document.getElementById(tabName + 'Tab');
22
+ if (content) {
23
+ content.classList.add('active');
24
+ }
25
+ }
26
+ };
27
+
28
+ // Initialize tabs when DOM is ready
29
+ document.addEventListener('DOMContentLoaded', function() {
30
+ TabManager.init();
31
+ });
32
+
33
+ // Export for global access
34
+ window.TabManager = TabManager;