Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/SmarkForm.esm.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/SmarkForm.esm.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/SmarkForm.umd.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/SmarkForm.umd.js.map

Large diffs are not rendered by default.

153 changes: 153 additions & 0 deletions src/lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,156 @@ export function replaceWrongNode(targetNode, error) {// {{{
targetNode.replaceWith(errorNode);
};// }}}

export function parseTime(str) {//{{{
// Accept "HH:mm" format (5 characters)
if (str.length === 5 && str[2] === ":") {
const hours = parseInt(str.substring(0, 2), 10);
const minutes = parseInt(str.substring(3, 5), 10);
if (
hours >= 0 && hours <= 23
&& minutes >= 0 && minutes <= 59
) {
return str + ":00"; // Add seconds
}
}

// Accept "HH:mm:ss" format (8 characters)
if (str.length === 8 && str[2] === ":" && str[5] === ":") {
const hours = parseInt(str.substring(0, 2), 10);
const minutes = parseInt(str.substring(3, 5), 10);
const seconds = parseInt(str.substring(6, 8), 10);
if (
hours >= 0 && hours <= 23
&& minutes >= 0 && minutes <= 59
&& seconds >= 0 && seconds <= 59
) {
return str;
}
}

// Accept "HHmmss" format (6 characters)
if (str.length === 6) {
const hours = parseInt(str.substring(0, 2), 10);
const minutes = parseInt(str.substring(2, 4), 10);
const seconds = parseInt(str.substring(4, 6), 10);
if (
hours >= 0 && hours <= 23
&& minutes >= 0 && minutes <= 59
&& seconds >= 0 && seconds <= 59
) {
return [
str.substring(0, 2),
str.substring(2, 4),
str.substring(4, 6),
].join(":");
}
}

// Accept "HHmm" format (4 characters)
if (str.length === 4) {
const hours = parseInt(str.substring(0, 2), 10);
const minutes = parseInt(str.substring(2, 4), 10);
if (
hours >= 0 && hours <= 23
&& minutes >= 0 && minutes <= 59
) {
return [
str.substring(0, 2),
str.substring(2, 4),
"00"
].join(":");
}
}

return null;
};//}}}

export function parseDateTime(str) {//{{{
// Accept "YYYYMMDDTHHmmss" format
if (str.length === 15 && str[8] === "T") {
const date = [
str.substring(0, 4),
str.substring(4, 6),
str.substring(6, 8),
].join("-");
const time = [
str.substring(9, 11),
str.substring(11, 13),
str.substring(13, 15),
].join(":");
return new Date(`${date}T${time}`);
}

// Accept "YYYYMMDDTHHmm" format
if (str.length === 13 && str[8] === "T") {
const date = [
str.substring(0, 4),
str.substring(4, 6),
str.substring(6, 8),
].join("-");
const time = [
str.substring(9, 11),
str.substring(11, 13),
"00",
].join(":");
return new Date(`${date}T${time}`);
}

// Accept "YYYY-MM-DDTHH:mm:ss" format (standard datetime-local format)
if (
str.length === 19
&& str[4] === "-"
&& str[7] === "-"
&& str[10] === "T"
&& str[13] === ":"
&& str[16] === ":"
) {
return new Date(str);
}

// Accept "YYYY-MM-DDTHH:mm" format (datetime-local without seconds)
if (
str.length === 16
&& str[4] === "-"
&& str[7] === "-"
&& str[10] === "T"
&& str[13] === ":"
) {
return new Date(str + ":00");
}

// Accept ISO 8601 strings with timezone info (like .toISOString() output)
// Example: "2023-12-25T14:30:45.789Z"
const isoMatch = str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/);
if (isoMatch) {
return new Date(str);
}

return NaN;
};//}}}

export function setTabIndex(target, value = "-1") {//{{{
// Set tabindex attribute only if not explicitly defined
if (target.getAttribute("tabindex") === null) {
target.setAttribute("tabindex", value);
}
};//}}}

export function validateInputType(targetFieldNode, expectedType, errorCode, errorMessage) {//{{{
// Validate that the target field is an INPUT element with the expected type
const targetTag = targetFieldNode.tagName;
const targetType = targetFieldNode.getAttribute("type");
if (
targetTag != "INPUT"
|| (targetType || expectedType).toLowerCase() != expectedType
) {
const error = new Error(errorMessage);
error.code = errorCode;
throw error;
}
// Autofill type attribute if not present
if (!targetType) {
targetFieldNode.type = expectedType;
}
};//}}}

21 changes: 11 additions & 10 deletions src/types/color.type.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// ===================
import {input} from "./input.type.js";
import {action} from "./trigger.type.js";
import {validateInputType} from "../lib/helpers.js";
const re_color = /^#([a-f0-9]{3}){1,2}$/i;
const disabled_style = `
opacity: .5;
Expand Down Expand Up @@ -33,16 +34,16 @@ export class color extends input {
if (me.isSingleton) return; // (Only for real field)

// Check targetField's type attribute:
const targetTag = me.targetFieldNode.tagName;
const targetType = me.targetFieldNode.getAttribute("type");
if (
targetTag != "INPUT"
|| (targetType || "color").toLowerCase() != "color"
) throw me.renderError(
'NOT_A_COLOR_FIELD'
, `Color inputs require an INPUT tag of type "color".`
);
if (! targetType) me.targetFieldNode.type = "color"; // Autofill
try {
validateInputType(
me.targetFieldNode,
"color",
'NOT_A_COLOR_FIELD',
`Color inputs require an INPUT tag of type "color".`
);
} catch (error) {
throw me.renderError(error.code, error.message);
}

// Iniitialize me.isDefined flag:
const valueAttr = me.targetFieldNode.getAttribute("value");
Expand Down
21 changes: 11 additions & 10 deletions src/types/date.type.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// ==================
import {input} from "./input.type.js";
import {action} from "./trigger.type.js";
import {validateInputType} from "../lib/helpers.js";
const re_timePart = /T.*/;
function parseDateStr(str) {//{{{
// Accept "YYYYMMDD":
Expand Down Expand Up @@ -36,16 +37,16 @@ export class date extends input {
async render() {//{{{
await super.render();
const me = this;
const targetTag = me.targetFieldNode.tagName;
const targetType = me.targetFieldNode.getAttribute("type");
if (
targetTag != "INPUT"
|| (targetType || "date").toLowerCase() != "date"
) throw me.renderError(
'NOT_A_DATE_FIELD'
, `Date inputs require an INPUT tag of type "date".`
);
if (! targetType) me.targetFieldNode.type = "date"; // Autofill
try {
validateInputType(
me.targetFieldNode,
"date",
'NOT_A_DATE_FIELD',
`Date inputs require an INPUT tag of type "date".`
);
} catch (error) {
throw me.renderError(error.code, error.message);
}
};//}}}
@action
// (Done in parent class) @export_to_target
Expand Down
89 changes: 13 additions & 76 deletions src/types/datetime-local.type.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,7 @@
// ==============================
import {input} from "./input.type.js";
import {action} from "./trigger.type.js";

function parseDateTimeStr(str) {//{{{
// Accept "YYYYMMDDTHHmmss" format
if (str.length === 15 && str[8] === "T") {
const date = [
str.substring(0, 4),
str.substring(4, 6),
str.substring(6, 8),
].join("-");
const time = [
str.substring(9, 11),
str.substring(11, 13),
str.substring(13, 15),
].join(":");
return new Date(`${date}T${time}`);
}

// Accept "YYYYMMDDTHHmm" format
if (str.length === 13 && str[8] === "T") {
const date = [
str.substring(0, 4),
str.substring(4, 6),
str.substring(6, 8),
].join("-");
const time = [
str.substring(9, 11),
str.substring(11, 13),
"00",
].join(":");
return new Date(`${date}T${time}`);
}

// Accept "YYYY-MM-DDTHH:mm:ss" format (standard datetime-local format)
if (
str.length === 19
&& str[4] === "-"
&& str[7] === "-"
&& str[10] === "T"
&& str[13] === ":"
&& str[16] === ":"
) {
return new Date(str);
}

// Accept "YYYY-MM-DDTHH:mm" format (datetime-local without seconds)
if (
str.length === 16
&& str[4] === "-"
&& str[7] === "-"
&& str[10] === "T"
&& str[13] === ":"
) {
return new Date(str + ":00");
}

// Accept ISO 8601 strings with timezone info (like .toISOString() output)
// Example: "2023-12-25T14:30:45.789Z"
const isoMatch = str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/);
if (isoMatch) {
return new Date(str);
}

return NaN;
};//}}}
import {parseDateTime, validateInputType} from "../lib/helpers.js";

function ISODateTimeLocal(value) {//{{{
// Format as YYYY-MM-DDTHH:mm:ss (local time, no timezone)
Expand All @@ -82,16 +19,16 @@ export class datetimeLocal extends input {
async render() {//{{{
await super.render();
const me = this;
const targetTag = me.targetFieldNode.tagName;
const targetType = me.targetFieldNode.getAttribute("type");
if (
targetTag != "INPUT"
|| (targetType || "datetime-local").toLowerCase() != "datetime-local"
) throw me.renderError(
'NOT_A_DATETIME_LOCAL_FIELD'
, `Datetime-local inputs require an INPUT tag of type "datetime-local".`
);
if (! targetType) me.targetFieldNode.type = "datetime-local"; // Autofill
try {
validateInputType(
me.targetFieldNode,
"datetime-local",
'NOT_A_DATETIME_LOCAL_FIELD',
`Datetime-local inputs require an INPUT tag of type "datetime-local".`
);
} catch (error) {
throw me.renderError(error.code, error.message);
}
};//}}}
@action
// (Done in parent class) @export_to_target
Expand All @@ -100,7 +37,7 @@ export class datetimeLocal extends input {
const data = await super.export(...args);
if (me.isSingleton) return data; // Overload only inner field
if (! data.length) return null;
const value = parseDateTimeStr(data);
const value = parseDateTime(data);
return (
isNaN(value) ? null
: ISODateTimeLocal(value)
Expand All @@ -115,7 +52,7 @@ export class datetimeLocal extends input {
data instanceof Date ? data // Accept Date instance
: typeof data == "number" ? new Date(data) // Accept epoch
: ! data || (typeof data != "string") ? NaN // Reject nullish
: parseDateTimeStr(data) // Handle strings
: parseDateTime(data) // Handle strings
);
const retv = await super.import((
isNaN(value) ? null
Expand Down
13 changes: 2 additions & 11 deletions src/types/list.type.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,11 @@ import {sortable} from "./list.decorators/sortable.deco.js";
import {export_to_target} from "../decorators/export_to_target.deco.js";
import {import_from_target} from "../decorators/import_from_target.deco.js";
import {mutex} from "../decorators/mutex.deco.js";
import {makeRoom, getRoots, parseJSON} from "../lib/helpers.js";
import {makeRoom, getRoots, parseJSON, setTabIndex} from "../lib/helpers.js";

// Private helpers:
// ----------------

function makeNonNavigable(target) {//{{{
if (
// Tabindex not explicitly defined:
target.getAttribute("tabindex") === null
) {
target.setAttribute("tabindex", "-1");
};
};//}}}

function loadTemplates(me) {//{{{
const templates = {};
for (const child of [...me.targetNode.children]) {
Expand Down Expand Up @@ -153,7 +144,7 @@ export class list extends SmarkField {
&& origin.options.hotkey
) {
// Skip them in keyboard navigation.
makeNonNavigable(origin.targetNode);
setTabIndex(origin.targetNode);
};
break;
};
Expand Down
Loading