mirror of
https://github.com/stfrigerio/sqliteDB.git
synced 2026-07-22 05:38:02 +00:00
pfft
This commit is contained in:
parent
d8cc77f863
commit
6550a6570e
3 changed files with 147 additions and 85 deletions
|
|
@ -13,27 +13,47 @@ function getDateNavigatorStyles(): string {
|
|||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} .date-navigator-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column; /* Stack nav row and button vertically */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--size-4-2); /* Space between nav row and button */
|
||||
gap: var(--size-4-2);
|
||||
}
|
||||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} .date-nav-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--size-4-3);
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} .date-nav-button {
|
||||
font-size: 2.5rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-normal);
|
||||
transition: color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} .date-nav-button:hover {
|
||||
color: var(--text-accent);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} h1.date-navigator-display {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
flex-grow: 1;
|
||||
font-size: var(--font-heading-1);
|
||||
color: var(--text-error);
|
||||
color: var(--text-normal);
|
||||
white-space: nowrap;
|
||||
min-width: 15ch;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import { createModalTriggerHandler } from "./eventHandlers/modalTriggerHandlers"
|
|||
import { TextInputConfig, ModalType, TextInputEventProps, TextInputDOMElements } from "./TextInput.types";
|
||||
import { loadTextValue } from "./data/loadTextValue";
|
||||
import { upsertTextValue } from "./data/upsertTextValue";
|
||||
import { updateInputValue, setTextErrorState, clearTextErrorState } from "./dom/textInputUiUpdaters";
|
||||
import { updateInputValue, setTextErrorState, clearTextErrorState, updateStaticTextUI } from "./dom/textInputUiUpdaters";
|
||||
import { DATE_CHANGED_EVENT_NAME } from "src/pluginState";
|
||||
|
||||
export class TextInput extends HTMLElement {
|
||||
// --- Dependencies ---
|
||||
|
|
@ -25,95 +26,98 @@ export class TextInput extends HTMLElement {
|
|||
public valueCol: string = "";
|
||||
public dateCol: string = "";
|
||||
|
||||
private _isListeningForDateChanges: boolean = false; // Prevent multiple listeners
|
||||
|
||||
|
||||
// --- Shadow DOM Elements ---
|
||||
private uiElements!: TextInputDOMElements;
|
||||
|
||||
// --- Lifecycle ---
|
||||
connectedCallback() {
|
||||
if (this._isInitialized) return;
|
||||
// Initialization requires appInstance
|
||||
|
||||
//~ 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() {
|
||||
if (this._isListeningForDateChanges) {
|
||||
document.removeEventListener(DATE_CHANGED_EVENT_NAME, this._handleGlobalDateChange);
|
||||
this._isListeningForDateChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Public Methods ---
|
||||
/** Injects DBService first, then App instance. */
|
||||
public setDependencies(dbService: DBService, app: App): void {
|
||||
if (!dbService || !app) {
|
||||
console.error("[TextInput] Invalid DBService or App instance provided.");
|
||||
//? Maybe show an error state on the element directly?
|
||||
this.textContent = "[Init Error]";
|
||||
return;
|
||||
}
|
||||
if (!dbService || !app) { /* ... error ... */ return; }
|
||||
this.appInstance = app;
|
||||
try {
|
||||
this.textDataService = new TextInputDataService(dbService); //^ Init Data Service
|
||||
} catch(error){
|
||||
console.error("[TextInput] Failed to initialize TextInputDataService:", error);
|
||||
this.textContent = "[Data Service Error]";
|
||||
return;
|
||||
}
|
||||
this.textDataService = new TextInputDataService(dbService);
|
||||
} catch (error) { /* ... error ... */ return; }
|
||||
|
||||
if (!this._isInitialized) {
|
||||
this._readAttributesAndBuildConfig();
|
||||
this._initializeComponent();
|
||||
this._isInitialized = true;
|
||||
}
|
||||
//? Now that dependencies are set, complete the setup
|
||||
this._readAttributesAndInitializeData();
|
||||
}
|
||||
|
||||
// --- Internal Initialization ---
|
||||
private _readAttributesAndBuildConfig(): void {
|
||||
this.config = {
|
||||
// UI Config
|
||||
label: this.getAttribute("data-label") ?? undefined,
|
||||
placeholder: this.getAttribute("placeholder") ?? undefined,
|
||||
initialValueAttr: this.getAttribute("data-initial-value") ?? "", // Read initial value from attr
|
||||
modalType: (this.getAttribute("data-modal-type") as ModalType) ?? 'none',
|
||||
isButton: this.getAttribute("data-is-button")?.toLowerCase() === 'true', //^ Read isButton flag
|
||||
/** 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") ?? "";
|
||||
|
||||
// Data Config
|
||||
table: this.getAttribute("data-table") ?? undefined,
|
||||
date: this.getAttribute("data-date") ?? "@date", // Default to @date
|
||||
valueCol: this.getAttribute("data-value-col") ?? undefined,
|
||||
dateCol: this.getAttribute("data-date-col") ?? undefined,
|
||||
};
|
||||
//? Copy needed values from config/attributes to 'this' properties
|
||||
this.table = this.config.table ?? "";
|
||||
this.date = this.config.date ?? "@date";
|
||||
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;
|
||||
|
||||
private _initializeComponent(): void {
|
||||
if (!this.appInstance) {
|
||||
console.error("[TextInput] Cannot initialize: App instance not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
const eventProps: TextInputEventProps = {
|
||||
...this.config, // Includes data config now
|
||||
app: this.appInstance,
|
||||
uiElements: this.uiElements, // Placeholder
|
||||
currentValue: this.currentValue,
|
||||
setValue: this._setValue,
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
handleInputChange: createInputChangeHandler(eventProps),
|
||||
handleModalTrigger: createModalTriggerHandler(eventProps),
|
||||
};
|
||||
|
||||
this.uiElements = buildTextInputDOM(this.attachShadow({ mode: "open" }), this.config, handlers);
|
||||
eventProps.uiElements = this.uiElements;
|
||||
applyTextInputStyles(this.shadowRoot!);
|
||||
|
||||
//^ --- Trigger Initial Load (if data configured) ---
|
||||
if (this.config.table && this.config.date && this.config.valueCol && this.config.dateCol) {
|
||||
loadTextValue(this).catch(err => console.error(`[TextInput ${this.config.table}] Unhandled error during initial load:`, err));
|
||||
} else {
|
||||
//? No data config, just use initialValueAttr
|
||||
this._updateDisplay(this.config.initialValueAttr ?? "");
|
||||
}
|
||||
|
||||
// todo: date change listener if needed
|
||||
// --- Trigger Initial Load ---
|
||||
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?.();
|
||||
}
|
||||
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. */
|
||||
|
|
@ -138,6 +142,34 @@ export class TextInput extends HTMLElement {
|
|||
}
|
||||
};
|
||||
|
||||
private _setupDateChangeListener(): void {
|
||||
const dateAttr = this.getAttribute("data-date"); // Read attribute directly
|
||||
if (dateAttr === "@date" && !this._isListeningForDateChanges) {
|
||||
document.addEventListener(DATE_CHANGED_EVENT_NAME, this._handleGlobalDateChange);
|
||||
this._isListeningForDateChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleGlobalDateChange = (event: Event): void => {
|
||||
//? Type guard for CustomEvent
|
||||
if (!(event instanceof CustomEvent)) return;
|
||||
|
||||
const newIsoDate = event.detail?.newIsoDate;
|
||||
|
||||
//? Double-check this component should react (uses @date)
|
||||
if (this.date === "@date") {
|
||||
//& console.log(`[TextInput ${this.uniqueId}] Date property matches "@date", reloading data...`);
|
||||
this.classList.add('loading');
|
||||
this._updateDisplay("..."); // Show loading dots
|
||||
|
||||
loadTextValue(this)
|
||||
.catch(err => { /* ... */ })
|
||||
.finally(() => { this.classList.remove('loading'); });
|
||||
} else {
|
||||
//& console.log(`[TextInput ${this.uniqueId}] Ignoring date change event (internal date property is "${this.date}").`);
|
||||
}
|
||||
};
|
||||
|
||||
//? Public method called by data helpers to update display
|
||||
public _updateDisplay(newValue: string): void {
|
||||
this.currentValue = newValue; // Update internal state
|
||||
|
|
@ -149,8 +181,9 @@ export class TextInput extends HTMLElement {
|
|||
//? Public methods for UI state helpers
|
||||
public showErrorState(message?: string): void { setTextErrorState(this, this.uiElements, message); }
|
||||
public clearErrorState(): void { clearTextErrorState(this, this.uiElements); }
|
||||
|
||||
// todo: disable/enable methods
|
||||
|
||||
private _enableInput(): void { if(this.uiElements?.inputElement) this.uiElements.inputElement.disabled = false; this.removeAttribute('aria-disabled'); }
|
||||
private _disableInput(): void { if(this.uiElements?.inputElement) this.uiElements.inputElement.disabled = true; this.setAttribute('aria-disabled', 'true'); }
|
||||
}
|
||||
|
||||
// --- Custom Element Definition ---
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { DBService } from "src/DBService";
|
||||
import { TextRecord, TextDataArgs, UpsertTextArgs } from "../TextInput/TextInput.types";
|
||||
import { TextDataArgs, UpsertTextArgs } from "../TextInput/TextInput.types";
|
||||
import { quoteSqlIdentifier } from "./utils/quoteSqlIdentifier";
|
||||
import { validateBaseTextArgs, validateUpsertTextArgs } from "./utils/validateTextInputArgs";
|
||||
|
||||
interface RawTextRecord {
|
||||
value: string | number | null;
|
||||
}
|
||||
|
||||
export class TextInputDataService {
|
||||
private dbService: DBService;
|
||||
constructor(dbService: DBService) {
|
||||
|
|
@ -10,7 +14,7 @@ export class TextInputDataService {
|
|||
this.dbService = dbService;
|
||||
}
|
||||
|
||||
/** //? Fetches the current text value for a specific key/date. Returns empty string if not found. */
|
||||
/** Fetches the current text value for a specific key/date. Returns empty string if not found. */
|
||||
async fetchTextValue(args: TextDataArgs): Promise<string> {
|
||||
try {
|
||||
validateBaseTextArgs(args);
|
||||
|
|
@ -21,10 +25,15 @@ export class TextInputDataService {
|
|||
const sql = `SELECT ${safeValueCol} AS value FROM ${safeTable} WHERE ${safeDateCol} = ?`;
|
||||
const params = [args.date];
|
||||
|
||||
const result = await this.dbService.getQuery<TextRecord>(sql, params);
|
||||
const result = await this.dbService.getQuery<RawTextRecord>(sql, params);
|
||||
const value = result?.[0]?.value;
|
||||
//? Return fetched string or empty string if null/undefined/not found
|
||||
return typeof value === 'string' ? value : "";
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
const stringValue = String(value); // Convert number or string using String()
|
||||
return stringValue;
|
||||
} else {
|
||||
return ""; // Return empty string for null/undefined/not found
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[TextInputDataService] Error in fetchTextValue for ${args.table}:`, error);
|
||||
|
|
@ -32,7 +41,7 @@ export class TextInputDataService {
|
|||
}
|
||||
}
|
||||
|
||||
/** //? Upserts the text value. Handles optional UUID column logic. */
|
||||
/** Upserts the text value. Handles optional UUID column logic. */
|
||||
async upsertTextValue(args: UpsertTextArgs): Promise<void> {
|
||||
try {
|
||||
validateUpsertTextArgs(args);
|
||||
|
|
|
|||
Loading…
Reference in a new issue