fixed button text input and time picker

This commit is contained in:
stfrigerio 2025-04-08 16:59:35 +02:00
parent 6550a6570e
commit 48cb74f09a
20 changed files with 381 additions and 182 deletions

View file

@ -1,6 +1,5 @@
// src/components/dateNavigator/dom/buildDateNavigatorDOM.ts
import { formatDateForDisplay, parseDateISO } from "../../datePicker/datePickerUtils";
import type { DateNavigatorDOMElements } from "../dateNavigator.types"; // Type only
import { formatDateForDisplay, parseDateISO } from "src/helpers/dateUtils";
import type { DateNavigatorDOMElements } from "../dateNavigator.types";
//? Structure for the handlers needed by the builder
export interface DateNavigatorHandlers {
@ -15,7 +14,7 @@ export function buildDateNavigatorDOM(
initialIsoDate: string,
handlers: DateNavigatorHandlers
): DateNavigatorDOMElements {
containerEl.empty(); // Clear container first
containerEl.empty();
const wrapper = containerEl.createDiv({ cls: "date-navigator-wrapper" });
const navRow = wrapper.createDiv({ cls: "date-nav-row" });

View file

@ -1,8 +1,7 @@
// src/components/dateNavigator/eventHandlers/dateNavigationHandlers.ts
import { App } from "obsidian";
import { DatePickerModal } from "../../datePicker/DatePickerModal";
import { DatePickerModal } from "src/components/DatePickerModal/DatePickerModal";
import { PluginState } from "src/pluginState";
import { parseDateISO, formatDateISO } from "../../datePicker/datePickerUtils";
import { parseDateISO, formatDateISO } from "src/helpers/dateUtils";
import { NavigationPeriod } from "../dateNavigator.types";
/** Updates the global plugin state with the new date and triggers necessary updates */
@ -10,13 +9,10 @@ function updateGlobalDate(newDate: Date, pluginState: PluginState, app: App): vo
const newIsoDate = formatDateISO(newDate);
if (pluginState.selectedDate !== newIsoDate) {
pluginState.selectedDate = newIsoDate;
// Optional: Force re-render if needed, maybe make this configurable
// app.workspace.getActiveViewOfType(MarkdownView)?.previewMode?.rerender(true);
}
//todo: Could add dispatchEvent here for more reactive updates elsewhere
}
/** //? Creates the handler for the "previous" button click */
/** Creates the handler for the "previous" button click */
export function createPrevHandler(
pluginState: PluginState,
period: NavigationPeriod,
@ -49,7 +45,6 @@ export function createNextHandler(
): () => void {
return () => {
const currentDate = parseDateISO(pluginState.selectedDate) ?? new Date();
//& console.log(`[DateNavHandlers] Next clicked. Current: ${formatDateISO(currentDate)}, Period: ${period}`);
switch (period) {
case 'day':
currentDate.setUTCDate(currentDate.getUTCDate() + 1);
@ -72,8 +67,6 @@ export function createOpenModalHandler(
): () => void {
return () => {
new DatePickerModal(app, pluginState.selectedDate, (newIsoDate) => {
//? This is the callback executed when the modal confirms a selection
//? No need to call updateGlobalDate here, modal already did via pluginState setter
updateDisplayCallback(newIsoDate);
}).open();
};

View file

@ -1,6 +1,6 @@
import { MarkdownRenderChild, App } from 'obsidian';
import { DateNavigator } from './components/dateNavigator/DateNavigator';
import { DateNavigator } from './DateNavigator';
import { pluginState } from '../../pluginState';
/**

View file

@ -3,7 +3,7 @@ import { buildDatePickerHeader } from "./dom/buildDatePickerHeader";
import { buildWeekdayHeaders } from "./dom/buildWeekdayHeaders";
import { buildCalendarGrid } from "./dom/buildCalendarGrid";
import { buildDatePickerFooter } from "./dom/buildDatePickerFooter";
import { formatDateISO, parseDateISO } from "./datePickerUtils";
import { formatDateISO, parseDateISO } from "src/helpers/dateUtils";
export class DatePickerModal extends Modal {
private selectedDateObj: Date; //? Internal state as Date object

View file

@ -1,4 +1,4 @@
import { getISOWeekNumber } from "../datePickerUtils";
import { getISOWeekNumber } from "src/helpers/dateUtils";
/**
* Builds the main grid of days for the calendar month.

View file

@ -1,5 +1,4 @@
// src/components/datePicker/dom/buildDatePickerFooter.ts
import { formatDateForDisplay } from "../datePickerUtils"; // Use consistent display formatting
import { formatDateForDisplay } from "src/helpers/dateUtils";
/**
* Builds the footer section of the date picker modal (Selected Date Display, Confirm Button).

View file

@ -1,67 +0,0 @@
import { App, Modal, Notice, Setting } from "obsidian";
//? A very basic example modal for selecting a time.
export class TimePickerModal extends Modal {
private currentTime: string; // e.g., "HH:MM"
private onSelectCallback: (selectedTime: string) => void;
constructor(app: App, initialTime: string | undefined, onSelect: (selectedTime: string) => void) {
super(app);
//? Basic validation or default
this.currentTime = initialTime && /^\d{2}:\d{2}$/.test(initialTime) ? initialTime : "12:00";
this.onSelectCallback = onSelect;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("time-picker-modal-content"); // For styling
contentEl.createEl("h2", { text: "Select Time" });
// --- Basic Time Input (replace with better UI later) ---
let tempTime = this.currentTime;
new Setting(contentEl)
.setName("Time (HH:MM)")
.addText(text => text
.setValue(tempTime)
.setPlaceholder("HH:MM")
.onChange(value => {
// Basic format check
if (/^\d{1,2}:\d{1,2}$/.test(value)) {
// Format to HH:MM
const parts = value.split(':');
const hh = parts[0].padStart(2,'0');
const mm = parts[1].padStart(2,'0');
// Basic hour/minute range check (can be improved)
if (parseInt(hh) >= 0 && parseInt(hh) <= 23 && parseInt(mm) >= 0 && parseInt(mm) <= 59) {
tempTime = `${hh}:${mm}`;
} else {
// Handle invalid range slightly? Or let confirm check.
}
}
}));
// --- Action Buttons ---
new Setting(contentEl)
.addButton(button => button
.setButtonText("Cancel")
.onClick(() => this.close()))
.addButton(button => button
.setButtonText("Confirm")
.setCta() // Make it the primary action
.onClick(() => {
//? Final validation before confirming
if (/^\d{2}:\d{2}$/.test(tempTime)) {
this.onSelectCallback(tempTime);
this.close();
} else {
new Notice("Invalid time format. Use HH:MM.");
}
}));
}
onClose() {
this.contentEl.empty();
}
}

View file

@ -0,0 +1,64 @@
import { App, Modal } from "obsidian";
import { buildTimePickerDOM, buildTimePickerActions, TimePickerDOMElements } from "./dom/buildTimePickerDOM";
import { parseTimeString, formatTimeString } from "./timePickerUtils";
export class TimePickerModal extends Modal {
private initialHour: number;
private initialMinute: number;
private onSelectCallback: (selectedTime: string) => void;
//? References to the dropdown elements
private uiElements: TimePickerDOMElements | null = null;
constructor(app: App, initialTime: string | undefined, onSelect: (selectedTime: string) => void) {
super(app);
this.onSelectCallback = onSelect;
//? Parse initial time into numbers
const { hour, minute } = parseTimeString(initialTime ?? ""); // Provide default if undefined
this.initialHour = hour;
this.initialMinute = minute;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("time-picker-modal-content"); // For styling
contentEl.createEl("h2", { text: "Select Time" });
// --- Build Dropdowns ---
this.uiElements = buildTimePickerDOM(contentEl, this.initialHour, this.initialMinute);
// --- Build Actions ---
buildTimePickerActions(contentEl, this._handleCancel, this._handleConfirm);
}
// --- Handlers ---
private _handleCancel = (): void => {
this.close();
};
private _handleConfirm = (): void => {
if (!this.uiElements) {
console.error("[TimePickerModal] Cannot confirm: UI elements not found.");
return;
}
//? Get current values from dropdowns
const selectedHour = this.uiElements.hourSelect.value;
const selectedMinute = this.uiElements.minuteSelect.value;
//? Format the selected time
const selectedTime = formatTimeString(selectedHour, selectedMinute);
this.onSelectCallback(selectedTime); // Pass HH:MM string back
this.close();
};
onClose() {
this.contentEl.empty();
this.uiElements = null; // Clear references
}
}

View file

@ -0,0 +1,66 @@
import { Setting } from "obsidian";
import { createHourOptions, createMinuteOptions } from "../timePickerUtils";
//? Interface for the references to the created select elements
export interface TimePickerDOMElements {
hourSelect: HTMLSelectElement;
minuteSelect: HTMLSelectElement;
}
/**
* Builds the core UI elements (dropdowns) for the Time Picker Modal.
* @param contentEl - The HTMLElement to append the controls to.
* @param initialHour - The initially selected hour (0-23).
* @param initialMinute - The initially selected minute (0-59).
* @returns Object containing references to the select elements.
*/
export function buildTimePickerDOM(
contentEl: HTMLElement,
initialHour: number,
initialMinute: number
): TimePickerDOMElements {
// Use Obsidian's Setting components for structure and alignment
const setting = new Setting(contentEl)
.setName("Select Time")
.setClass("time-picker-controls"); // Add class for specific styling
// --- Hour Dropdown ---
const hourSelect = document.createElement("select");
hourSelect.classList.add("dropdown", "time-picker-select", "time-picker-hour");
hourSelect.setAttribute("aria-label", "Hour");
hourSelect.appendChild(createHourOptions(initialHour)); // Populate options
setting.controlEl.appendChild(hourSelect); // Add to setting's control area
// --- Separator ---
const separator = document.createElement("span");
separator.textContent = ":";
separator.addClass("time-picker-separator");
setting.controlEl.appendChild(separator);
// --- Minute Dropdown ---
const minuteSelect = document.createElement("select");
minuteSelect.classList.add("dropdown", "time-picker-select", "time-picker-minute");
minuteSelect.setAttribute("aria-label", "Minute");
minuteSelect.appendChild(createMinuteOptions(initialMinute)); // Populate options
setting.controlEl.appendChild(minuteSelect);
return { hourSelect, minuteSelect };
}
/** Builds the action buttons (Cancel, Confirm) for the modal. */
export function buildTimePickerActions(
contentEl: HTMLElement,
onCancel: () => void,
onConfirm: () => void
): void {
new Setting(contentEl)
.setClass("time-picker-actions") // Add class for styling
.addButton(button => button
.setButtonText("Cancel")
.onClick(onCancel))
.addButton(button => button
.setButtonText("Confirm")
.setCta() // Make it the primary action
.onClick(onConfirm));
}

View file

@ -0,0 +1,60 @@
/** Generates <option> elements for hours (00-23). */
export function createHourOptions(selectedHour: number): DocumentFragment {
const fragment = document.createDocumentFragment();
for (let i = 0; i < 24; i++) {
const option = document.createElement("option");
const hourString = String(i).padStart(2, '0');
option.value = hourString;
option.textContent = hourString;
if (i === selectedHour) {
option.selected = true;
}
fragment.appendChild(option);
}
return fragment;
}
/** //? Generates <option> elements for minutes (00-59). */
export function createMinuteOptions(selectedMinute: number): DocumentFragment {
const fragment = document.createDocumentFragment();
for (let i = 0; i < 60; i++) {
const option = document.createElement("option");
const minuteString = String(i).padStart(2, '0');
option.value = minuteString;
option.textContent = minuteString;
if (i === selectedMinute) {
option.selected = true;
}
fragment.appendChild(option);
}
return fragment;
}
/** //? Parses "HH:MM" string into hours and minutes numbers. */
export function parseTimeString(timeString: string): { hour: number; minute: number } {
let hour = 12; // Default hour
let minute = 0; // Default minute
if (/^\d{1,2}:\d{1,2}$/.test(timeString)) {
const parts = timeString.split(':').map(Number);
const parsedHour = parts[0];
const parsedMinute = parts[1];
if (!isNaN(parsedHour) && parsedHour >= 0 && parsedHour <= 23) {
hour = parsedHour;
}
if (!isNaN(parsedMinute) && parsedMinute >= 0 && parsedMinute <= 59) {
minute = parsedMinute;
}
}
//& console.log(`[TimePickerUtils] Parsed "${timeString}" to H:${hour}, M:${minute}`);
return { hour, minute };
}
/** //? Formats hour and minute numbers into "HH:MM" string. */
export function formatTimeString(hour: number | string, minute: number | string): string {
const hh = String(hour).padStart(2, '0');
const mm = String(minute).padStart(2, '0');
return `${hh}:${mm}`;
}

View file

@ -0,0 +1,54 @@
const STYLE_ID = "sqlite-db-time-picker-styles";
function getTimePickerStyles(): string {
return `
.time-picker-modal-content {
padding: var(--size-4-4) var(--size-4-6);
}
.time-picker-modal-content h2 {
text-align: center;
margin-bottom: var(--size-4-4);
}
/* Style the setting containing the dropdowns */
.time-picker-controls .setting-item-control {
display: flex;
justify-content: center; /* Center the dropdowns */
align-items: center;
gap: var(--size-4-1); /* Space between hour, :, minute */
}
.time-picker-select {
min-width: 60px; /* Give dropdowns some width */
text-align: center;
}
.time-picker-separator {
font-size: var(--font-ui-large);
font-weight: bold;
line-height: var(--input-height); /* Align with dropdowns */
padding: 0 var(--size-4-1);
}
/* Style the setting containing the buttons */
.time-picker-actions {
margin-top: var(--size-4-4);
border-top: 1px solid var(--background-modifier-border);
padding-top: var(--size-4-3);
}
.time-picker-actions .setting-item-control {
justify-content: flex-end; /* Align buttons right */
}
`;
}
/** Injects CSS for the Time Picker modal into the document head. */
export function injectTimePickerStyles(): void {
if (document.getElementById(STYLE_ID)) return;
const styleEl = document.createElement('style');
styleEl.id = STYLE_ID;
styleEl.textContent = getTimePickerStyles();
document.head.appendChild(styleEl);
}
/** Removes the injected Time Picker styles. */
export function removeTimePickerStyles(): void {
const styleEl = document.getElementById(STYLE_ID);
if (styleEl) styleEl.remove();
}

View file

@ -11,15 +11,20 @@ import { upsertTextValue } from "./data/upsertTextValue";
import { updateInputValue, setTextErrorState, clearTextErrorState, updateStaticTextUI } from "./dom/textInputUiUpdaters";
import { DATE_CHANGED_EVENT_NAME } from "src/pluginState";
let textInputCounter = 0;
export class TextInput extends HTMLElement {
// --- Dependencies ---
private appInstance: App | null = null;
public appInstance: App | null = null;
public textDataService: TextInputDataService | null = null;
// --- State & Config ---
public config: TextInputConfig = {};
public currentValue: string = "";
private _isInitialized: boolean = false;
private _domBuilt: boolean = false; // Tracks if Shadow DOM exists
private _initialLoadTriggered: boolean = false; // Tracks if initial load has been triggered
//^ data related state/config
public table: string = "";
public date: string = "";
@ -28,54 +33,26 @@ export class TextInput extends HTMLElement {
private _isListeningForDateChanges: boolean = false; // Prevent multiple listeners
// --- Shadow DOM Elements ---
private uiElements!: TextInputDOMElements;
private uniqueId: string; // For logging/debugging
constructor() {
super();
this.uniqueId = `ti-${textInputCounter++}`;
}
// --- Lifecycle ---
connectedCallback() {
//& console.log(`[TextInput ${this.uniqueId}] connectedCallback.`);
//? We only mark that the element is in the DOM.
//? Initialization waits for dependencies via setDependencies.
//? Setup listener only after attributes are read.
if (this._isInitialized) return;
//~ Initialization logic will be triggered by setDependencies,
//~ but we need to build the DOM structure and setup listeners that
//~ depend only on attributes right away.
//~ Create bound event handler (needed by DOM builder if input acts as trigger)
const eventProps: TextInputEventProps = { // Partial props okay here? Or defer fully? Let's defer.
app: this.appInstance!, // Assume app will be set, riskier
uiElements: this.uiElements, // Placeholder
currentValue: this.currentValue,
setValue: this._setValue,
// Config will be filled later
};
const handlers = {
handleInputChange: createInputChangeHandler(eventProps), // Needs props later
handleModalTrigger: createModalTriggerHandler(eventProps), // Needs props later
};
//? Read config attributes needed *immediately* for DOM build (like isButton, modalType)
const initialConfig: TextInputConfig = {
modalType: (this.getAttribute("data-modal-type") as ModalType) ?? 'none',
isButton: this.getAttribute("data-is-button")?.toLowerCase() === 'true',
// Read others needed by buildTextInputDOM, possibly placeholder/initialValueAttr too
placeholder: this.getAttribute("placeholder") ?? undefined,
initialValueAttr: this.getAttribute("data-initial-value") ?? "",
label: this.getAttribute("data-label") ?? undefined,
};
//? Build DOM structure immediately
this.uiElements = buildTextInputDOM(this.attachShadow({ mode: "open" }), initialConfig, handlers);
applyTextInputStyles(this.shadowRoot!);
//? This reads the attribute directly, safe to call now.
this._setupDateChangeListener();
this._isInitialized = true; // Mark basic init done
}
disconnectedCallback() {
//& console.log(`[TextInput ${this.uniqueId}] disconnectedCallback - Cleaning up listener.`);
if (this._isListeningForDateChanges) {
document.removeEventListener(DATE_CHANGED_EVENT_NAME, this._handleGlobalDateChange);
this._isListeningForDateChanges = false;
@ -85,66 +62,111 @@ export class TextInput extends HTMLElement {
// --- Public Methods ---
/** Injects DBService first, then App instance. */
public setDependencies(dbService: DBService, app: App): void {
if (!dbService || !app) { /* ... error ... */ return; }
//& console.log(`[TextInput ${this.uniqueId}] setDependencies called.`);
if (this._isInitialized) {
//& console.log(`[TextInput ${this.uniqueId}] Already initialized, skipping dependency set.`);
return; // Don't re-initialize if already done
}
if (!dbService || !app) {
console.error("[TextInput] Invalid DBService or App instance provided during dependency injection.");
this.textContent = "[Init Error - Dependencies]";
return;
}
this.appInstance = app;
try {
this.textDataService = new TextInputDataService(dbService);
} catch (error) { /* ... error ... */ return; }
} catch (error) {
console.error(`[TextInput ${this.uniqueId}] Failed to initialize TextInputDataService:`, error);
this.textContent = "[Data Service Error]";
return;
}
//? Now that dependencies are set, complete the setup
this._readAttributesAndInitializeData();
//? Start the setup process now that dependencies are available
this._readAttributesAndBuildConfig(); // Reads attributes, sets 'this.date', setups listener
this._initializeComponent(); // Builds DOM, creates handlers, applies styles, triggers load
this._isInitialized = true; // Mark initialization complete
//& console.log(`[TextInput ${this.uniqueId}] Initialization fully complete.`);
}
/** Reads data-related attributes and triggers initial load. Called AFTER dependencies are set. */
private _readAttributesAndInitializeData(): void {
// --- Read Data Attributes ---
this.table = this.getAttribute("data-table") ?? "";
this.date = this.getAttribute("data-date") ?? "@date";
this.valueCol = this.getAttribute("data-value-col") ?? "";
this.dateCol = this.getAttribute("data-date-col") ?? "";
private _readAttributesAndBuildConfig(): void {
// --- Read ALL Attributes ---
this.config = {
label: this.getAttribute("data-label") ?? undefined,
placeholder: this.getAttribute("placeholder") ?? undefined,
initialValueAttr: this.getAttribute("data-initial-value") ?? "",
modalType: (this.getAttribute("data-modal-type") as ModalType) ?? 'none',
isButton: this.getAttribute("data-is-button")?.toLowerCase() === 'true',
table: this.getAttribute("data-table") ?? undefined,
date: this.getAttribute("data-date") ?? "@date",
valueCol: this.getAttribute("data-value-col") ?? undefined,
dateCol: this.getAttribute("data-date-col") ?? undefined,
};
//? Copy to component properties
this.table = this.config.table ?? "";
this.date = this.config.date ?? "@date"; // Store initial date config here
this.valueCol = this.config.valueCol ?? "";
this.dateCol = this.config.dateCol ?? "";
// --- Update Config object
this.config.table = this.table;
this.config.date = this.date;
this.config.valueCol = this.valueCol;
this.config.dateCol = this.dateCol;
//^ --- Setup Listener AFTER reading attributes and setting this.date ---
this._setupDateChangeListener(); // Safe to call now
}
// --- Trigger Initial Load ---
/** Builds DOM, creates handlers, applies styles, triggers initial load. */
private _initializeComponent(): void {
if (!this.appInstance) { console.error("[TextInput] Cannot initialize component: App instance missing."); return; }
if (this.shadowRoot) { console.warn(`[TextInput ${this.config.label || this.uniqueId}] Already has shadowRoot, skipping DOM build.`); return } // Prevent rebuilding DOM
//? Pass 'this' which now contains the fully populated config
const handlers = {
handleInputChange: createInputChangeHandler(this),
handleModalTrigger: createModalTriggerHandler(this),
};
//~ Build DOM and get element references
this.uiElements = buildTextInputDOM(this.attachShadow({ mode: "open" }), this.config, handlers);
//~ Apply styles
applyTextInputStyles(this.shadowRoot!); // shadowRoot definitely exists now
this._domBuilt = true; // Mark DOM built
// --- Update Static UI & Trigger Initial Load (Deferred slightly) ---
requestAnimationFrame(() => {
updateStaticTextUI(this.uiElements, this.config.label);
if (!this.table || !this.valueCol || !this.dateCol) {
console.warn(`[TextInput ${this.config.label}] Missing required data configuration attributes (table, valueCol, dateCol).`);
this.showErrorState("Config Error"); this._disableInput?.();
updateStaticTextUI(this.uiElements, this.config.label);
// --- Data Validation & Load Trigger ---
if (this.table && this.valueCol && this.dateCol) {
// Config looks okay for data operations
if (!this._initialLoadTriggered) {
this._initialLoadTriggered = true;
loadTextValue(this).catch(err => console.error(`[TextInput ${this.config.label || this.uniqueId}] Unhandled error during initial load:`, err));
}
} else {
console.warn(`[TextInput ${this.config.label || this.uniqueId}] Missing required data configuration attributes (table, valueCol, dateCol). Will use initialValueAttr.`);
this._updateDisplay(this.config.initialValueAttr ?? "");
this._disableInput?.();
}
loadTextValue(this).catch(err => console.error(`[TextInput ${this.config.label}] Unhandled error during initial load:`, err));
});
}
/** Central method to update state, UI, and trigger save. */
private _setValue = (newValue: string, triggerSave: boolean = false): void => {
public _setValue = (newValue: string, triggerSave: boolean = false): void => {
if (this.currentValue !== newValue) {
this.currentValue = newValue;
//? Always update the input display
this._updateDisplay(this.currentValue);
this.dispatchEvent(new CustomEvent('input-change', { detail: { value: this.currentValue }, /*...*/ }));
//? Trigger save only if configured and requested
if (triggerSave && this.config.table && this.config.date && this.config.valueCol && this.config.dateCol) {
//? Call the upsert helper
upsertTextValue(this).catch(err => {
console.error(`[TextInput ${this.config.table}] Error during save:`, err);
//? Consider reverting UI or showing persistent error?
// this.showErrorState("Save Failed");
});
this._updateDisplay(this.currentValue); // Update UI optimistically
this.dispatchEvent(new CustomEvent('input-change', { /* ... */ }));
// Use 'this' properties for validation
if (triggerSave && this.table && this.valueCol && this.dateCol) {
//& console.log(`[TextInput ${this.config.label || this.uniqueId}] Save triggered.`);
upsertTextValue(this).catch(/* ... */);
}
}
};
//? Listener Setup (called from _readAttributesAndBuildConfig)
private _setupDateChangeListener(): void {
const dateAttr = this.getAttribute("data-date"); // Read attribute directly
if (dateAttr === "@date" && !this._isListeningForDateChanges) {
if (this.date === "@date" && !this._isListeningForDateChanges) {
document.addEventListener(DATE_CHANGED_EVENT_NAME, this._handleGlobalDateChange);
this._isListeningForDateChanges = true;
}

View file

@ -1,13 +1,15 @@
import type { TextInputEventProps } from "../TextInput.types";
import type { TextInput } from "../TextInput";
export type InputChangeEventHandler = (event: Event) => void;
/** Creates the handler for the input element's 'change' event. */
export function createInputChangeHandler(props: TextInputEventProps): InputChangeEventHandler {
export function createInputChangeHandler(instance: TextInput): InputChangeEventHandler {
return (event: Event) => {
if (event.target instanceof HTMLInputElement) {
const newValue = event.target.value;
props.setValue(newValue, true);
//? Access component's method directly
//? Pass true to trigger save on 'change' event
instance._setValue(newValue, true);
}
};
}

View file

@ -1,34 +1,41 @@
import type { TextInputEventProps } from "../TextInput.types";
import { TimePickerModal } from "../../../components/TimePickerModal";
import { DatePickerModal } from "../../../codeblocks/dateHeader/components/datePicker/DatePickerModal";
import type { TextInput } from "../TextInput";
import { DatePickerModal } from "src/components/DatePickerModal/DatePickerModal";
import { TimePickerModal } from "src/components/TimePickerModal/TimePickerModal";
export type ModalTriggerHandler = (event: MouseEvent) => void;
/** //? Creates the handler for the modal trigger element's 'click' event. */
export function createModalTriggerHandler(props: TextInputEventProps): ModalTriggerHandler { // Use renamed type
//^ Accept the component instance 'this' instead of props object
export function createModalTriggerHandler(instance: TextInput): ModalTriggerHandler {
return (event: MouseEvent) => {
// No debug log needed at start
const modalType = instance.config.modalType; // Get current modalType from config
const app = instance.appInstance; // Get app instance
const currentValue = instance.currentValue; // Get current value
const label = instance.config.label; // Get label for logging
switch (props.modalType) {
if (!app) {
console.error(`[ModalTriggerHandler ${label}] Cannot open modal: App instance is missing.`);
return;
}
switch (modalType) {
case 'time-picker':
new TimePickerModal(props.app, props.currentValue, (selectedTime: string) => {
props.setValue(selectedTime, true);
new TimePickerModal(app, currentValue, (selectedTime: string) => {
//? Use component's method to set value
instance._setValue(selectedTime, true);
}).open();
break;
case 'date-picker':
const initialDate = props.currentValue || new Date().toISOString().split('T')[0];
new DatePickerModal(props.app, initialDate, (selectedDate: string) => {
props.setValue(selectedDate, true);
const initialDate = currentValue || new Date().toISOString().split('T')[0];
new DatePickerModal(app, initialDate, (selectedDate: string) => {
instance._setValue(selectedDate, true);
}).open();
break;
// todo: Add cases for 'list-select', 'custom', etc.
case 'none':
default:
// Do nothing or console.warn if unexpected click
// console.warn(`[ModalTriggerHandler ${props.label}] Clicked trigger but modalType is '${props.modalType}'.`);
console.warn(`[ModalTriggerHandler ${label}] Clicked trigger but modalType is '${modalType}'.`);
break;
}
};