11import type { App , Debouncer } from "obsidian" ;
22import { TextComponent , debounce } from "obsidian" ;
33import GenericInputPrompt from "../GenericInputPrompt/GenericInputPrompt" ;
4- import { parseNaturalLanguageDate } from "../../utils/dateParser" ;
4+ import { createDatePicker , type DatePickerController } from "../date-picker/datePicker" ;
5+ import { formatISODate , parseNaturalLanguageDate } from "../../utils/dateParser" ;
56import { settingsStore } from "../../settingsStore" ;
67import {
78 formatDateAliasInline ,
@@ -15,6 +16,9 @@ export default class VDateInputPrompt extends GenericInputPrompt {
1516 private currentInput : string ;
1617 private isOpen = true ;
1718 private defaultValue : string | undefined ;
19+ private datePicker ?: DatePickerController ;
20+ private selectedIso ?: string ;
21+ private lastPickerDisplayValue ?: string ;
1822 private static readonly PREVIEW_PLACEHOLDER = "Preview will appear here" ;
1923
2024 public static Prompt (
@@ -43,29 +47,29 @@ export default class VDateInputPrompt extends GenericInputPrompt {
4347 ) {
4448 // Pass the defaultValue to the parent so the input box is pre-filled
4549 super ( app , header , placeholder , defaultValue ?? "" ) ;
46-
50+
51+ this . containerEl . addClass ( "qaDatePrompt" ) ;
4752 this . dateFormat = dateFormat || "YYYY-MM-DD" ;
4853 this . defaultValue = defaultValue ;
4954 this . currentInput = defaultValue ?? "" ;
50-
55+
5156 // Create debounced preview update function (250ms delay, reset on each call)
5257 this . updatePreviewDebounced = debounce (
5358 this . updatePreview . bind ( this ) ,
5459 250 ,
5560 true // Reset timer on each call (standard debounce behavior)
5661 ) ;
5762
58- // Trigger initial preview update now that all fields are properly set
59- if ( this . defaultValue ) {
60- this . updatePreview ( ) ;
61- }
63+ this . updatePreview ( ) ;
6264 }
6365
6466 protected createInputField (
6567 container : HTMLElement ,
6668 placeholder ?: string ,
6769 value ?: string
6870 ) {
71+ container . addClass ( "qa-date-input" ) ;
72+
6973 // Create TextComponent directly to avoid duplicate onChange listeners
7074 const textComponent = new TextComponent ( container ) ;
7175
@@ -74,6 +78,7 @@ export default class VDateInputPrompt extends GenericInputPrompt {
7478 . setPlaceholder ( placeholder ?? "" )
7579 . setValue ( value ?? "" )
7680 . onChange ( ( newValue ) => {
81+ this . lastPickerDisplayValue = undefined ;
7782 this . onInputChanged ( newValue ) ;
7883 this . currentInput = newValue ;
7984 this . updatePreviewDebounced ( ) ;
@@ -82,13 +87,30 @@ export default class VDateInputPrompt extends GenericInputPrompt {
8287
8388 // Initialize currentInput with the initial value (which should be defaultValue)
8489 this . currentInput = value ?? "" ;
85-
90+
91+ this . createDatePicker ( container ) ;
92+
8693 // Create preview element
8794 this . createPreviewElement ( container ) ;
88-
95+
8996 return textComponent ;
9097 }
9198
99+ private createDatePicker ( container : HTMLElement ) {
100+ const pickerContainer = container . createDiv ( {
101+ cls : "qa-date-picker-container" ,
102+ } ) ;
103+
104+ this . datePicker = createDatePicker ( {
105+ container : pickerContainer ,
106+ initialIso : this . selectedIso ,
107+ onSelect : ( iso ) => {
108+ if ( iso ) this . applyPickerSelection ( iso ) ;
109+ else this . clearPickerSelection ( ) ;
110+ } ,
111+ } ) ;
112+ }
113+
92114 private createPreviewElement ( container : HTMLElement ) {
93115 const previewContainer = container . createDiv ( "vdate-preview-container" ) ;
94116 previewContainer . style . marginTop = "0.5rem" ;
@@ -143,38 +165,109 @@ export default class VDateInputPrompt extends GenericInputPrompt {
143165 private updatePreview ( ) {
144166 // Don't update if modal is closed
145167 if ( ! this . isOpen ) return ;
146-
168+
147169 const input = this . currentInput . trim ( ) ;
148-
170+
149171 // If no input and we have a default, show preview for default
150172 if ( ! input && this . defaultValue ) {
151- this . renderPreview ( this . defaultValue ) ;
173+ this . renderPreviewFromInput ( this . defaultValue ) ;
152174 return ;
153175 }
154-
176+
155177 if ( ! input ) {
178+ this . selectedIso = undefined ;
179+ this . lastPickerDisplayValue = undefined ;
180+ this . syncPickerSelection ( ) ;
156181 this . setPreviewText ( VDateInputPrompt . PREVIEW_PLACEHOLDER , false ) ;
157182 return ;
158183 }
159-
184+
185+ if ( input . startsWith ( "@date:" ) ) {
186+ const iso = input . slice ( 6 ) . trim ( ) ;
187+ if ( iso ) {
188+ this . selectedIso = iso ;
189+ this . lastPickerDisplayValue = undefined ;
190+ this . syncPickerSelection ( iso ) ;
191+ this . renderPreviewFromIso ( iso ) ;
192+ return ;
193+ }
194+ }
195+
196+ if (
197+ this . selectedIso &&
198+ this . lastPickerDisplayValue &&
199+ input === this . lastPickerDisplayValue
200+ ) {
201+ this . syncPickerSelection ( this . selectedIso , false ) ;
202+ this . renderPreviewFromIso ( this . selectedIso ) ;
203+ return ;
204+ }
205+
160206 // If input matches default value or regular input, render the preview
161- this . renderPreview ( input ) ;
207+ this . renderPreviewFromInput ( input ) ;
162208 }
163209
164- private renderPreview ( value : string ) {
210+ private formatIsoForInput ( iso : string ) : string {
211+ const formatted = formatISODate ( iso , this . dateFormat ) ;
212+ if ( formatted ) return formatted ;
213+ return iso . length >= 10 ? iso . slice ( 0 , 10 ) : iso ;
214+ }
215+
216+ private syncPickerSelection ( iso ?: string , updateView = true ) {
217+ this . datePicker ?. setSelectedIso ( iso , { updateView } ) ;
218+ }
219+
220+ private applyPickerSelection ( iso : string ) {
221+ const displayValue = this . formatIsoForInput ( iso ) ;
222+ this . selectedIso = iso ;
223+ this . lastPickerDisplayValue = displayValue ;
224+ if ( this . inputComponent ?. inputEl ) {
225+ this . inputComponent . inputEl . value = displayValue ;
226+ }
227+ this . onInputChanged ( displayValue ) ;
228+ this . currentInput = displayValue ;
229+ this . syncPickerSelection ( iso ) ;
230+ this . renderPreviewFromIso ( iso ) ;
231+ }
232+
233+ private clearPickerSelection ( ) {
234+ if ( this . inputComponent ?. inputEl ) {
235+ this . inputComponent . inputEl . value = "" ;
236+ }
237+ this . onInputChanged ( "" ) ;
238+ this . currentInput = "" ;
239+ this . selectedIso = undefined ;
240+ this . lastPickerDisplayValue = undefined ;
241+ this . syncPickerSelection ( ) ;
242+ this . updatePreview ( ) ;
243+ }
244+
245+ private renderPreviewFromIso ( iso : string ) {
246+ this . setPreviewText ( this . formatIsoForInput ( iso ) , false ) ;
247+ }
248+
249+ private renderPreviewFromInput ( value : string ) {
165250 const parseResult = parseNaturalLanguageDate ( value , this . dateFormat ) ;
166-
167- if ( parseResult . isValid && parseResult . formatted ) {
168- this . setPreviewText ( parseResult . formatted , false ) ;
251+
252+ if ( parseResult . isValid && parseResult . isoString ) {
253+ this . selectedIso = parseResult . isoString ;
254+ this . lastPickerDisplayValue = undefined ;
255+ this . syncPickerSelection ( parseResult . isoString ) ;
256+ const formatted =
257+ parseResult . formatted ?? this . formatIsoForInput ( parseResult . isoString ) ;
258+ this . setPreviewText ( formatted , false ) ;
169259 } else {
260+ this . selectedIso = undefined ;
261+ this . lastPickerDisplayValue = undefined ;
262+ this . syncPickerSelection ( ) ;
170263 const errorMessage = parseResult . error || "Unable to parse date" ;
171264 this . setPreviewText ( errorMessage , true ) ;
172265 }
173266 }
174267
175268 private setPreviewText ( text : string , isError : boolean ) {
176269 this . previewEl . textContent = text ;
177-
270+
178271 if ( isError ) {
179272 this . previewEl . style . color = "var(--text-error)" ;
180273 } else {
@@ -186,18 +279,46 @@ export default class VDateInputPrompt extends GenericInputPrompt {
186279 super . onOpen ( ) ;
187280 }
188281
282+ protected transformInputOnSubmit ( input : string ) : string {
283+ const trimmed = input . trim ( ) ;
284+ if ( trimmed . startsWith ( "@date:" ) ) return trimmed ;
285+ if (
286+ this . selectedIso &&
287+ this . lastPickerDisplayValue &&
288+ trimmed === this . lastPickerDisplayValue
289+ ) {
290+ return `@date:${ this . selectedIso } ` ;
291+ }
292+ if ( ! trimmed && this . defaultValue ) {
293+ const parsed = parseNaturalLanguageDate (
294+ this . defaultValue ,
295+ this . dateFormat ,
296+ ) ;
297+ if ( parsed . isValid && parsed . isoString ) {
298+ return `@date:${ parsed . isoString } ` ;
299+ }
300+ }
301+ if ( trimmed ) {
302+ const parsed = parseNaturalLanguageDate ( trimmed , this . dateFormat ) ;
303+ if ( parsed . isValid && parsed . isoString ) {
304+ return `@date:${ parsed . isoString } ` ;
305+ }
306+ }
307+ return input ;
308+ }
309+
189310 onClose ( ) {
190311 // Prevent any pending debounced updates
191312 this . isOpen = false ;
192-
313+
193314 // Cancel any pending debounced calls
194315 this . updatePreviewDebounced . cancel ( ) ;
195-
316+
196317 // If input is empty and we have a default, use the default
197318 if ( ! this . input . trim ( ) && this . defaultValue ) {
198319 this . input = this . defaultValue ;
199320 }
200-
321+
201322 super . onClose ( ) ;
202323 }
203324}
0 commit comments